diff options
874 files changed, 30337 insertions, 10695 deletions
diff --git a/Android.bp b/Android.bp index 908280e5d7f3..72fc1030b63b 100644 --- a/Android.bp +++ b/Android.bp @@ -344,8 +344,8 @@ filegroup { genrule { name: "statslog-telephony-common-java-gen", tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --java $(out) --module telephony_common" - + " --javaPackage com.android.internal.telephony --javaClass TelephonyCommonStatsLog", + cmd: "$(location stats-log-api-gen) --java $(out) --module telephony_common" + + " --javaPackage com.android.internal.telephony --javaClass TelephonyCommonStatsLog", out: ["com/android/internal/telephony/TelephonyCommonStatsLog.java"], } @@ -421,6 +421,7 @@ filegroup { ":resourcemanager_aidl", ":storaged_aidl", ":vold_aidl", + ":deviceproductinfoconstants_aidl", // For the generated R.java and Manifest.java ":framework-res{.aapt.srcjar}", @@ -580,9 +581,11 @@ java_library { "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", + "android.hardware.vibrator-V2-java", "android.security.apc-java", "android.security.authorization-java", "android.security.usermanager-java", + "android.security.vpnprofilestore-java", "android.system.keystore2-V1-java", "android.system.suspend.control.internal-java", "cameraprotosnano", @@ -749,8 +752,8 @@ java_library { } platform_compat_config { - name: "framework-platform-compat-config", - src: ":framework-minus-apex", + name: "framework-platform-compat-config", + src: ":framework-minus-apex", } // A temporary build target that is conditionally included on the bootclasspath if @@ -771,7 +774,7 @@ genrule { name: "statslog-framework-java-gen", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module framework" + - " --javaPackage com.android.internal.util --javaClass FrameworkStatsLog --worksource", + " --javaPackage com.android.internal.util --javaClass FrameworkStatsLog --worksource", out: ["com/android/internal/util/FrameworkStatsLog.java"], } @@ -880,7 +883,7 @@ filegroup { java_library { name: "framework-annotations-lib", - srcs: [ ":framework-annotations" ], + srcs: [":framework-annotations"], sdk_version: "core_current", } @@ -1158,7 +1161,6 @@ cc_library { }, } - // This is the full proto version of libplatformprotos. It may only // be used by test code that is not shipped on the device. cc_library { @@ -1224,68 +1226,57 @@ filegroup { path: "core/java", } -aidl_interface { - name: "libincremental_aidl", - unstable: true, +cc_defaults { + name: "incremental_default", + cflags: [ + "-Wall", + "-Wextra", + "-Wextra-semi", + "-Werror", + "-Wzero-as-null-pointer-constant", + "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", + ], + shared_libs: [ + "libbinder", + "libutils", + ], + aidl: { + include_dirs: [ + "frameworks/native/aidl/binder", + ], + export_aidl_headers: true, + }, +} + +cc_library { + name: "libincremental_aidl-cpp", srcs: [ ":incremental_aidl", ], - backend: { - java: { - sdk_version: "28", - }, - cpp: { - enabled: true, - }, - ndk: { - enabled: true, - }, - }, + defaults: ["incremental_default"], } -aidl_interface { - name: "libdataloader_aidl", - unstable: true, +cc_library { + name: "libdataloader_aidl-cpp", srcs: [ ":dataloader_aidl", ], - imports: [ - "libincremental_aidl", + defaults: ["incremental_default"], + shared_libs: [ + "libincremental_aidl-cpp", ], - backend: { - java: { - sdk_version: "28", - }, - cpp: { - enabled: true, - }, - ndk: { - enabled: false, - }, - }, } -aidl_interface { - name: "libincremental_manager_aidl", - unstable: true, +cc_library { + name: "libincremental_manager_aidl-cpp", srcs: [ ":incremental_manager_aidl", ], - imports: [ - "libincremental_aidl", - "libdataloader_aidl", + defaults: ["incremental_default"], + shared_libs: [ + "libincremental_aidl-cpp", + "libdataloader_aidl-cpp", ], - backend: { - java: { - sdk_version: "28", - }, - cpp: { - enabled: true, - }, - ndk: { - enabled: false, - }, - }, } // TODO(b/77285514): remove this once the last few hidl interfaces have been @@ -1314,7 +1305,7 @@ java_library { "core/java/android/os/RemoteException.java", "core/java/android/util/AndroidException.java", ], - libs: [ "unsupportedappusage" ], + libs: ["unsupportedappusage"], dxflags: ["--core-library"], installable: false, @@ -1533,4 +1524,5 @@ java_library { ":protolog-common-src", ], } + // protolog end diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 30ed7de92614..175fb38bafc7 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,5 +1,6 @@ [Builtin Hooks] clang_format = true +bpfmt = true [Builtin Hooks Options] # Only turn on clang-format check for the following subfolders. @@ -15,7 +16,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp services/incremental/ tests/ tools/ - +bpfmt = -d [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java index e83c64c37678..5a4596108aa5 100644 --- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java +++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java @@ -44,7 +44,7 @@ import java.io.OutputStream; @RunWith(AndroidJUnit4.class) public class TypefaceCreatePerfTest { // A font file name in asset directory. - private static final String TEST_FONT_NAME = "DancingScript-Regular.ttf"; + private static final String TEST_FONT_NAME = "DancingScript.ttf"; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt index 1d94d7e8eca2..d5ed95f18f93 100644 --- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt @@ -97,11 +97,21 @@ class PackageParsingPerfTest { private val state: BenchmarkState get() = perfStatusReporter.benchmarkState private val apks: List<File> get() = params.apks + private fun safeParse(parser: ParallelParser<*>, file: File) { + try { + parser.parse(file) + } catch (e: Exception) { + // ignore + } + } + @Test fun sequentialNoCache() { params.cacheDirToParser(null).use { parser -> while (state.keepRunning()) { - apks.forEach { parser.parse(it) } + apks.forEach { + safeParse(parser, it) + } } } } @@ -110,10 +120,10 @@ class PackageParsingPerfTest { fun sequentialCached() { params.cacheDirToParser(testFolder.newFolder()).use { parser -> // Fill the cache - apks.forEach { parser.parse(it) } + apks.forEach { safeParse(parser, it) } while (state.keepRunning()) { - apks.forEach { parser.parse(it) } + apks.forEach { safeParse(parser, it) } } } } @@ -132,7 +142,7 @@ class PackageParsingPerfTest { fun parallelCached() { params.cacheDirToParser(testFolder.newFolder()).use { parser -> // Fill the cache - apks.forEach { parser.parse(it) } + apks.forEach { safeParse(parser, it) } while (state.keepRunning()) { apks.forEach { parser.submit(it) } diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index 168c7c2f13cd..5ded446ff81a 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -207,7 +207,6 @@ package android.app.appsearch { public static final class GetByUriRequest.Builder { ctor public GetByUriRequest.Builder(); - method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.lang.String...); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.lang.String...); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>); @@ -323,7 +322,6 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.lang.String...); method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.util.Collection<java.lang.String>); - method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.lang.String...); method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.SearchSpec build(); method @NonNull public android.app.appsearch.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_SIZE_LIMIT) int); @@ -338,7 +336,8 @@ package android.app.appsearch { public final class SetSchemaRequest { method @NonNull public java.util.Map<java.lang.String,android.app.appsearch.AppSearchSchema.Migrator> getMigrators(); method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas(); - method @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi(); + method @NonNull public java.util.Set<java.lang.String> getSchemasNotDisplayedBySystem(); + method @Deprecated @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi(); method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages(); method public boolean isForceOverride(); } @@ -350,8 +349,9 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SetSchemaRequest build(); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.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 @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean); + method @Deprecated @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean); } public class SetSchemaResponse { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 6a5975ef7ff7..da446bf1e7c6 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -317,7 +317,7 @@ public class AppSearchManager { getPackageName(), DEFAULT_DATABASE_NAME, schemaBundles, - new ArrayList<>(request.getSchemasNotVisibleToSystemUi()), + new ArrayList<>(request.getSchemasNotDisplayedBySystem()), /*schemasPackageAccessible=*/ Collections.emptyMap(), request.isForceOverride(), mContext.getUserId(), diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 24cc60e5eef5..f379739a3778 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -146,7 +146,7 @@ public final class AppSearchSession implements Closeable { mPackageName, mDatabaseName, schemaBundles, - new ArrayList<>(request.getSchemasNotVisibleToSystemUi()), + new ArrayList<>(request.getSchemasNotDisplayedBySystem()), schemasPackageAccessibleBundles, request.isForceOverride(), mUserId, diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java index 8dd9dc1be312..fb63e16aa6a8 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java @@ -96,7 +96,7 @@ public class GlobalSearchSession implements Closeable { * SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} when building a schema. * * <p>Document access can also be granted to system UIs by specifying {@link - * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} when building a schema. + * SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem} when building a schema. * * <p>See {@link AppSearchSession#search} for a detailed explanation on forming a query string. * diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index 68ae23f9a9db..ba27762c7dfb 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -33,7 +33,7 @@ interface IAppSearchManager { * @param packageName The name of the package that owns this schema. * @param databaseName The name of the database where this schema lives. * @param schemaBundles List of {@link AppSearchSchema} bundles. - * @param schemasNotPlatformSurfaceable Schema types that should not be surfaced on platform + * @param schemasNotDisplayedBySystem Schema types that should not be surfaced on platform * surfaces. * @param schemasPackageAccessibleBundles Schema types that are visible to the specified * packages. The value List contains PackageIdentifier Bundles. @@ -47,7 +47,7 @@ interface IAppSearchManager { in String packageName, in String databaseName, in List<Bundle> schemaBundles, - in List<String> schemasNotPlatformSurfaceable, + in List<String> schemasNotDisplayedBySystem, in Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, in int userId, 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 c927e3412cbc..17266f82b603 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java @@ -166,29 +166,7 @@ public final class GetByUriRequest { * all results, excepting any types that have their own, specific property paths set. * * @throws IllegalStateException if the builder has already been used. - * <p>{@see SearchSpec.Builder#addProjection(String, String...)} - */ - @NonNull - public Builder addProjection(@NonNull String schemaType, @NonNull String... propertyPaths) { - Preconditions.checkNotNull(propertyPaths); - return addProjection(schemaType, Arrays.asList(propertyPaths)); - } - - /** - * Adds property paths for the specified type to be used for projection. If property paths - * are added for a type, then only the properties referred to will be retrieved for results - * of that type. If a property path that is specified isn't present in a result, it will be - * ignored for that result. Property paths cannot be null. - * - * <p>If no property paths are added for a particular type, then all properties of results - * of that type will be retrieved. - * - * <p>If property path is added for the {@link - * GetByUriRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will apply to - * all results, excepting any types that have their own, specific property paths set. - * - * @throws IllegalStateException if the builder has already been used. - * <p>{@see SearchSpec.Builder#addProjection(String, String...)} + * @see SearchSpec.Builder#addProjection */ @NonNull public Builder addProjection( 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 d792f450bc05..f34034b1c5c0 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java @@ -202,14 +202,6 @@ public final class SearchResult { */ public static final String PROPERTY_PATH_FIELD = "propertyPath"; - /** - * The index of matching value in its property. A property may have multiple values. This - * index indicates which value is the match. - * - * @hide - */ - public static final String VALUES_INDEX_FIELD = "valuesIndex"; - /** @hide */ public static final String EXACT_MATCH_POSITION_LOWER_FIELD = "exactMatchPositionLower"; @@ -232,8 +224,7 @@ public final class SearchResult { mBundle = Preconditions.checkNotNull(bundle); Preconditions.checkNotNull(document); mPropertyPath = Preconditions.checkNotNull(bundle.getString(PROPERTY_PATH_FIELD)); - mFullText = - getPropertyValues(document, mPropertyPath, mBundle.getInt(VALUES_INDEX_FIELD)); + mFullText = getPropertyValues(document, mPropertyPath); } /** @@ -326,8 +317,7 @@ public final class SearchResult { } /** Extracts the matching string from the document. */ - private static String getPropertyValues( - GenericDocument document, String propertyName, int valueIndex) { + private static String getPropertyValues(GenericDocument document, String propertyName) { // In IcingLib snippeting is available for only 3 data types i.e String, double and // long, so we need to check which of these three are requested. // TODO (tytytyww): getPropertyStringArray takes property name, handle for property @@ -337,7 +327,9 @@ public final class SearchResult { if (values == null) { throw new IllegalStateException("No content found for requested property path!"); } - return values[valueIndex]; + + // TODO(b/175146044): Return the proper match based on the index in the propertyName. + return values[0]; } } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java index f72f8e1bbc5d..7888c8d78cd8 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java @@ -535,28 +535,6 @@ public final class SearchSpec { */ @NonNull public SearchSpec.Builder addProjection( - @NonNull String schema, @NonNull String... propertyPaths) { - Preconditions.checkNotNull(propertyPaths); - return addProjection(schema, Arrays.asList(propertyPaths)); - } - - /** - * Adds property paths for the specified type to be used for projection. If property paths - * are added for a type, then only the properties referred to will be retrieved for results - * of that type. If a property path that is specified isn't present in a result, it will be - * ignored for that result. Property paths cannot be null. - * - * <p>If no property paths are added for a particular type, then all properties of results - * of that type will be retrieved. - * - * <p>If property path is added for the {@link SearchSpec#PROJECTION_SCHEMA_TYPE_WILDCARD}, - * then those property paths will apply to all results, excepting any types that have their - * own, specific property paths set. - * - * <p>{@see SearchSpec.Builder#addProjection(String, String...)} - */ - @NonNull - public SearchSpec.Builder addProjection( @NonNull String schema, @NonNull Collection<String> propertyPaths) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(schema); 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 426a903981b3..c05406358995 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java @@ -71,19 +71,19 @@ import java.util.Set; */ public final class SetSchemaRequest { private final Set<AppSearchSchema> mSchemas; - private final Set<String> mSchemasNotVisibleToSystemUi; + private final Set<String> mSchemasNotDisplayedBySystem; private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages; private final Map<String, AppSearchSchema.Migrator> mMigrators; private final boolean mForceOverride; SetSchemaRequest( @NonNull Set<AppSearchSchema> schemas, - @NonNull Set<String> schemasNotVisibleToSystemUi, + @NonNull Set<String> schemasNotDisplayedBySystem, @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages, @NonNull Map<String, AppSearchSchema.Migrator> migrators, boolean forceOverride) { mSchemas = Preconditions.checkNotNull(schemas); - mSchemasNotVisibleToSystemUi = Preconditions.checkNotNull(schemasNotVisibleToSystemUi); + mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem); mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages); mMigrators = Preconditions.checkNotNull(migrators); mForceOverride = forceOverride; @@ -96,12 +96,22 @@ public final class SetSchemaRequest { } /** + * TODO(b/181887768): This method exists only for dogfooder transition and must be removed. + * @deprecated This method exists only for dogfooder transition and must be removed. + */ + @Deprecated + @NonNull + public Set<String> getSchemasNotVisibleToSystemUi() { + return getSchemasNotDisplayedBySystem(); + } + + /** * Returns all the schema types that are opted out of being displayed and visible on any system * UI surface. */ @NonNull - public Set<String> getSchemasNotVisibleToSystemUi() { - return Collections.unmodifiableSet(mSchemasNotVisibleToSystemUi); + public Set<String> getSchemasNotDisplayedBySystem() { + return Collections.unmodifiableSet(mSchemasNotDisplayedBySystem); } /** @@ -151,7 +161,7 @@ public final class SetSchemaRequest { */ public static final class Builder { private final Set<AppSearchSchema> mSchemas = new ArraySet<>(); - private final Set<String> mSchemasNotVisibleToSystemUi = new ArraySet<>(); + 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<>(); @@ -163,6 +173,8 @@ public final class SetSchemaRequest { * * <p>An {@link AppSearchSchema} object represents one type of structured data. * + * <p>Any documents of these types will be displayed on system UI surfaces by default. + * * @throws IllegalStateException if the builder has already been used. */ @NonNull @@ -187,30 +199,43 @@ public final class SetSchemaRequest { } /** + * TODO(b/181887768): This method exists only for dogfooder transition and must be removed. + * @deprecated This method exists only for dogfooder transition and must be removed. + */ + @Deprecated + @NonNull + public Builder setSchemaTypeVisibilityForSystemUi( + @NonNull String schemaType, boolean displayed) { + return setSchemaTypeDisplayedBySystem(schemaType, displayed); + } + + /** * Sets whether or not documents from the provided {@code schemaType} will be displayed and * visible on any system UI surface. * * <p>This setting applies to the provided {@code schemaType} only, and does not persist * across {@link AppSearchSession#setSchema} calls. * - * <p>By default, documents are displayed and visible on system UI surfaces. + * <p>The default behavior, if this method is not called, is to allow types to be displayed + * on system UI surfaces. * - * @param schemaType The schema type to set visibility on. - * @param visible Whether the {@code schemaType} will be visible or not. + * @param schemaType The name of an {@link AppSearchSchema} within the same {@link + * SetSchemaRequest}, which will be configured. + * @param displayed Whether documents of this type will be displayed on system UI surfaces. * @throws IllegalStateException if the builder has already been used. */ - // Merged list available from getSchemasNotVisibleToSystemUi + // Merged list available from getSchemasNotDisplayedBySystem @SuppressLint("MissingGetterMatchingBuilder") @NonNull - public Builder setSchemaTypeVisibilityForSystemUi( - @NonNull String schemaType, boolean visible) { + public Builder setSchemaTypeDisplayedBySystem( + @NonNull String schemaType, boolean displayed) { Preconditions.checkNotNull(schemaType); Preconditions.checkState(!mBuilt, "Builder has already been used"); - if (visible) { - mSchemasNotVisibleToSystemUi.remove(schemaType); + if (displayed) { + mSchemasNotDisplayedBySystem.remove(schemaType); } else { - mSchemasNotVisibleToSystemUi.add(schemaType); + mSchemasNotDisplayedBySystem.add(schemaType); } return this; } @@ -315,9 +340,10 @@ public final class SetSchemaRequest { Preconditions.checkState(!mBuilt, "Builder has already been used"); mBuilt = true; - // Verify that any schema types with visibility settings refer to a real schema. + // Verify that any schema types with display or visibility settings refer to a real + // schema. // Create a copy because we're going to remove from the set for verification purposes. - Set<String> referencedSchemas = new ArraySet<>(mSchemasNotVisibleToSystemUi); + Set<String> referencedSchemas = new ArraySet<>(mSchemasNotDisplayedBySystem); referencedSchemas.addAll(mSchemasVisibleToPackages.keySet()); for (AppSearchSchema schema : mSchemas) { @@ -332,7 +358,7 @@ public final class SetSchemaRequest { return new SetSchemaRequest( mSchemas, - mSchemasNotVisibleToSystemUi, + mSchemasNotDisplayedBySystem, mSchemasVisibleToPackages, mMigrators, mForceOverride); 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 a45fa39bd58d..27c9ccb2cca6 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -89,7 +89,7 @@ public class AppSearchManagerService extends SystemService { @NonNull String packageName, @NonNull String databaseName, @NonNull List<Bundle> schemaBundles, - @NonNull List<String> schemasNotPlatformSurfaceable, + @NonNull List<String> schemasNotDisplayedBySystem, @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, @UserIdInt int userId, @@ -119,13 +119,12 @@ public class AppSearchManagerService extends SystemService { } schemasPackageAccessible.put(entry.getKey(), packageIdentifiers); } - AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); impl.setSchema( packageName, databaseName, schemas, - schemasNotPlatformSurfaceable, + schemasNotDisplayedBySystem, schemasPackageAccessible, forceOverride); invokeCallbackOnResult( @@ -153,7 +152,7 @@ public class AppSearchManagerService extends SystemService { verifyUserUnlocked(callingUserId); verifyCallingPackage(callingUid, packageName); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName); List<Bundle> schemaBundles = new ArrayList<>(schemas.size()); for (int i = 0; i < schemas.size(); i++) { @@ -188,7 +187,7 @@ public class AppSearchManagerService extends SystemService { AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); for (int i = 0; i < documentBundles.size(); i++) { GenericDocument document = new GenericDocument(documentBundles.get(i)); try { @@ -231,7 +230,7 @@ public class AppSearchManagerService extends SystemService { AppSearchBatchResult.Builder<String, Bundle> resultBuilder = new AppSearchBatchResult.Builder<>(); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { @@ -276,7 +275,7 @@ public class AppSearchManagerService extends SystemService { verifyUserUnlocked(callingUserId); verifyCallingPackage(callingUid, packageName); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); SearchResultPage searchResultPage = impl.query( packageName, @@ -311,7 +310,7 @@ public class AppSearchManagerService extends SystemService { verifyUserUnlocked(callingUserId); verifyCallingPackage(callingUid, packageName); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); SearchResultPage searchResultPage = impl.globalQuery( queryExpression, @@ -342,7 +341,7 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(callingUserId); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); SearchResultPage searchResultPage = impl.getNextPage(nextPageToken); invokeCallbackOnResult( callback, @@ -362,7 +361,7 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(callingUserId); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); impl.invalidateNextPageToken(nextPageToken); } catch (Throwable t) { Log.e(TAG, "Unable to invalidate the query page token", t); @@ -390,7 +389,7 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(callingUserId); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis); invokeCallbackOnResult( callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); @@ -422,7 +421,7 @@ public class AppSearchManagerService extends SystemService { AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { @@ -460,7 +459,7 @@ public class AppSearchManagerService extends SystemService { verifyUserUnlocked(callingUserId); verifyCallingPackage(callingUid, packageName); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); impl.removeByQuery( packageName, databaseName, @@ -482,7 +481,7 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(callingUserId); AppSearchImpl impl = - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getAppSearchImpl(callingUserId); impl.persistToDisk(); } catch (Throwable t) { Log.e(TAG, "Unable to persist the data to disk", t); @@ -499,7 +498,7 @@ public class AppSearchManagerService extends SystemService { final long callingIdentity = Binder.clearCallingIdentity(); try { verifyUserUnlocked(callingUserId); - mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId); + mImplInstanceManager.getOrCreateAppSearchImpl(getContext(), callingUserId); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); @@ -571,6 +570,7 @@ public class AppSearchManagerService extends SystemService { private void invokeCallbackOnError( IAppSearchBatchResultCallback callback, Throwable throwable) { try { + //TODO(b/175067650) verify ParcelableException could propagate throwable correctly. callback.onSystemError(new ParcelableException(throwable)); } catch (RemoteException e) { Log.e(TAG, "Unable to send error to the callback", e); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index 82319d4f353b..8c953d12fd98 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -74,7 +74,7 @@ public final class ImplInstanceManager { } /** - * Gets an instance of AppSearchImpl for the given user. + * Gets an instance of AppSearchImpl for the given user, or creates one if none exists. * * <p>If no AppSearchImpl instance exists for the unlocked user, Icing will be initialized and * one will be created. @@ -84,7 +84,7 @@ public final class ImplInstanceManager { * @return An initialized {@link AppSearchImpl} for this user */ @NonNull - public AppSearchImpl getAppSearchImpl( + public AppSearchImpl getOrCreateAppSearchImpl( @NonNull Context context, @UserIdInt int userId) throws AppSearchException { synchronized (mInstancesLocked) { AppSearchImpl instance = mInstancesLocked.get(userId); @@ -96,6 +96,32 @@ public final class ImplInstanceManager { } } + + /** + * Gets an instance of AppSearchImpl for the given user. + * + * <p>This method should only be called by an initialized SearchSession, which has been already + * created the AppSearchImpl instance for the given user. + * + * @param userId The multi-user userId of the device user calling AppSearch + * @return An initialized {@link AppSearchImpl} for this user + * @throws IllegalStateException if {@link AppSearchImpl} haven't created for the given user. + */ + @NonNull + public AppSearchImpl getAppSearchImpl(@UserIdInt int userId) { + synchronized (mInstancesLocked) { + AppSearchImpl instance = mInstancesLocked.get(userId); + if (instance == null) { + // Impossible scenario, user cannot call an uninitialized SearchSession, + // getInstance should always find the instance for the given user and never try to + // create an instance for this user again. + throw new IllegalStateException( + "AppSearchImpl has never been created for this user: " + userId); + } + return instance; + } + } + private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId) throws AppSearchException { File appSearchDir = getAppSearchDir(context, userId); @@ -104,6 +130,7 @@ public final class ImplInstanceManager { private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) { // See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs + //TODO(b/177685938):Switch from getDataUserCePackageDirectory to getDataSystemCeDirectory File userCeDir = Environment.getDataUserCePackageDirectory( StorageManager.UUID_PRIVATE_INTERNAL, userId, context.getPackageName()); 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 f422ebc2db7b..e9852aa1cd41 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 @@ -113,8 +113,6 @@ public class SearchResultToProtoConverter { Bundle bundle = new Bundle(); bundle.putString(SearchResult.MatchInfo.PROPERTY_PATH_FIELD, propertyPath); bundle.putInt( - SearchResult.MatchInfo.VALUES_INDEX_FIELD, snippetMatchProto.getValuesIndex()); - bundle.putInt( SearchResult.MatchInfo.EXACT_MATCH_POSITION_LOWER_FIELD, snippetMatchProto.getExactMatchPosition()); bundle.putInt( diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 2c9477a5d76b..41c70f072262 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -I593dfd22279739e5f578e07d36a55cf02ee942c5 +I42b89416968565ceb6483b400894f5b49524208c diff --git a/apex/appsearch/testing/Android.bp b/apex/appsearch/testing/Android.bp index eb072afec696..3c5082ef937d 100644 --- a/apex/appsearch/testing/Android.bp +++ b/apex/appsearch/testing/Android.bp @@ -30,5 +30,8 @@ java_library { "guava", "truth-prebuilt", ], - visibility: ["//cts/tests/appsearch"], + visibility: [ + "//cts/tests/appsearch", + "//vendor:__subpackages__", + ], } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java index d912c08e7d5f..440050faaa7d 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java @@ -34,8 +34,8 @@ public interface GlobalSearchSessionShim extends Closeable { * SetSchemaRequest.Builder#setDocumentClassVisibilityForPackage} when building a schema. * * <p>Document access can also be granted to system UIs by specifying {@link - * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi}, or {@link - * SetSchemaRequest.Builder#setDocumentClassVisibilityForSystemUi} when building a schema. + * SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem}, or {@link + * SetSchemaRequest.Builder#setDocumentClassDisplayedBySystem} when building a schema. * * <p>See {@link AppSearchSessionShim#search} for a detailed explanation on forming a query * string. diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java index df690d00a322..b1b733a599c6 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java @@ -108,6 +108,8 @@ public class PowerWhitelistManager { * @hide */ public static final int REASON_DENIED = -1; + + /* Reason code range 0-9 are reserved for default reasons */ /** * The default reason code if reason is unknown. */ @@ -116,6 +118,8 @@ public class PowerWhitelistManager { * Use REASON_OTHER if there is no better choice. */ public static final int REASON_OTHER = 1; + + /* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */ /** @hide */ public static final int REASON_PROC_STATE_PERSISTENT = 10; /** @hide */ @@ -128,6 +132,8 @@ public class PowerWhitelistManager { public static final int REASON_PROC_STATE_FGS = 14; /** @hide */ public static final int REASON_PROC_STATE_BFGS = 15; + + /* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */ /** @hide */ public static final int REASON_UID_VISIBLE = 50; /** @hide */ @@ -166,114 +172,126 @@ public class PowerWhitelistManager { public static final int REASON_EXEMPTED_PACKAGE = 64; /** @hide */ public static final int REASON_ALLOWLISTED_PACKAGE = 65; - /** - * If it's because of a role, - * @hide - */ + /** @hide */ public static final int REASON_APPOP = 66; /* BG-FGS-launch is allowed by temp-allowlist or system-allowlist. - Reason code for temp and system allowlist starts here. - */ + Reason code for temp and system allowlist starts here. + Reason code range 100-199 are reserved for public reasons. */ + /** + * Set temp-allowlist for location geofence purpose. + */ public static final int REASON_GEOFENCING = 100; + /** + * Set temp-allowlist for server push messaging. + */ public static final int REASON_PUSH_MESSAGING = 101; - public static final int REASON_ACTIVITY_RECOGNITION = 102; + /** + * Set temp-allowlist for server push messaging over the quota. + */ + public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; + /** + * Set temp-allowlist for activity recognition. + */ + public static final int REASON_ACTIVITY_RECOGNITION = 103; + /* Reason code range 200-299 are reserved for broadcast actions */ /** * Broadcast ACTION_BOOT_COMPLETED. * @hide */ - public static final int REASON_BOOT_COMPLETED = 103; + public static final int REASON_BOOT_COMPLETED = 200; /** * Broadcast ACTION_PRE_BOOT_COMPLETED. * @hide */ - public static final int REASON_PRE_BOOT_COMPLETED = 104; - + public static final int REASON_PRE_BOOT_COMPLETED = 201; /** * Broadcast ACTION_LOCKED_BOOT_COMPLETED. * @hide */ - public static final int REASON_LOCKED_BOOT_COMPLETED = 105; + public static final int REASON_LOCKED_BOOT_COMPLETED = 202; + + /* 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 = 106; + public static final int REASON_SYSTEM_ALLOW_LISTED = 300; /** @hide */ - public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 107; + public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301; /** * AlarmManagerService. * @hide */ - public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 108; + public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 302; /** * ActiveServices. * @hide */ - public static final int REASON_SERVICE_LAUNCH = 109; + public static final int REASON_SERVICE_LAUNCH = 303; /** * KeyChainSystemService. * @hide */ - public static final int REASON_KEY_CHAIN = 110; + public static final int REASON_KEY_CHAIN = 304; /** * PackageManagerService. * @hide */ - public static final int REASON_PACKAGE_VERIFIER = 111; + public static final int REASON_PACKAGE_VERIFIER = 305; /** * SyncManager. * @hide */ - public static final int REASON_SYNC_MANAGER = 112; + public static final int REASON_SYNC_MANAGER = 306; /** * DomainVerificationProxyV1. * @hide */ - public static final int REASON_DOMAIN_VERIFICATION_V1 = 113; + public static final int REASON_DOMAIN_VERIFICATION_V1 = 307; /** * DomainVerificationProxyV2. * @hide */ - public static final int REASON_DOMAIN_VERIFICATION_V2 = 114; + public static final int REASON_DOMAIN_VERIFICATION_V2 = 308; /** @hide */ - public static final int REASON_VPN = 115; + public static final int REASON_VPN = 309; /** * NotificationManagerService. * @hide */ - public static final int REASON_NOTIFICATION_SERVICE = 116; + public static final int REASON_NOTIFICATION_SERVICE = 310; /** * Broadcast ACTION_MY_PACKAGE_REPLACED. * @hide */ - public static final int REASON_PACKAGE_REPLACED = 117; + public static final int REASON_PACKAGE_REPLACED = 311; /** * LocationProviderManager. * @hide */ - public static final int REASON_LOCATION_PROVIDER = 118; + public static final int REASON_LOCATION_PROVIDER = 312; /** * MediaButtonReceiver. * @hide */ - public static final int REASON_MEDIA_BUTTON = 119; + public static final int REASON_MEDIA_BUTTON = 313; /** * InboundSmsHandler. * @hide */ - public static final int REASON_EVENT_SMS = 120; + public static final int REASON_EVENT_SMS = 314; /** * InboundSmsHandler. * @hide */ - public static final int REASON_EVENT_MMS = 121; + public static final int REASON_EVENT_MMS = 315; /** * Shell app. * @hide */ - public static final int REASON_SHELL = 122; + public static final int REASON_SHELL = 316; /** * The list of BG-FGS-Launch and temp-allowlist reason code. @@ -310,6 +328,7 @@ public class PowerWhitelistManager { // temp and system allowlist reasons. REASON_GEOFENCING, REASON_PUSH_MESSAGING, + REASON_PUSH_MESSAGING_OVER_QUOTA, REASON_ACTIVITY_RECOGNITION, REASON_BOOT_COMPLETED, REASON_PRE_BOOT_COMPLETED, @@ -589,6 +608,8 @@ public class PowerWhitelistManager { 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: diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java index 5e5717d11432..0dde546e85de 100644 --- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java @@ -32,12 +32,8 @@ public interface DeviceIdleInternal { void exitIdle(String reason); - // duration in milliseconds - void addPowerSaveTempWhitelistApp(int callingUid, String packageName, - long duration, int userId, boolean sync, String reason); - void addPowerSaveTempWhitelistApp(int callingUid, String packageName, - long duration, int userId, boolean sync, @ReasonCode int reasonCode, + long durationMs, int userId, boolean sync, @ReasonCode int reasonCode, @Nullable String reason); /** @@ -49,10 +45,11 @@ public interface DeviceIdleInternal { * @param sync * @param reasonCode one of {@link ReasonCode} * @param reason + * @param callingUid UID of app who added this temp-allowlist. */ void addPowerSaveTempWhitelistAppDirect(int uid, long duration, @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, - @Nullable String reason); + @Nullable String reason, int callingUid); // duration in milliseconds long getNotificationAllowlistDuration(); diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index ac28e828eb2e..84fb39b273c4 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -1943,19 +1943,11 @@ public class DeviceIdleController extends SystemService } // duration in milliseconds - @Deprecated @Override public void addPowerSaveTempWhitelistApp(int callingUid, String packageName, - long duration, int userId, boolean sync, @Nullable String reason) { - addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration, - userId, sync, REASON_UNKNOWN, reason); - } - - @Override - public void addPowerSaveTempWhitelistApp(int callingUid, String packageName, - long duration, int userId, boolean sync, @ReasonCode int reasonCode, + long durationMs, int userId, boolean sync, @ReasonCode int reasonCode, @Nullable String reason) { - addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration, + addPowerSaveTempAllowlistAppInternal(callingUid, packageName, durationMs, userId, sync, reasonCode, reason); } @@ -1963,8 +1955,8 @@ public class DeviceIdleController extends SystemService @Override public void addPowerSaveTempWhitelistAppDirect(int uid, long duration, @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, - @Nullable String reason) { - addPowerSaveTempWhitelistAppDirectInternal(0, uid, duration, type, sync, + @Nullable String reason, int callingUid) { + addPowerSaveTempWhitelistAppDirectInternal(callingUid, uid, duration, type, sync, reasonCode, reason); } @@ -2741,6 +2733,16 @@ public class DeviceIdleController extends SystemService void addPowerSaveTempAllowlistAppInternal(int callingUid, String packageName, long duration, int userId, boolean sync, @ReasonCode int reasonCode, @Nullable String reason) { + synchronized (this) { + int callingAppId = UserHandle.getAppId(callingUid); + if (callingAppId >= Process.FIRST_APPLICATION_UID) { + if (!mPowerSaveWhitelistSystemAppIds.get(callingAppId)) { + throw new SecurityException( + "Calling app " + UserHandle.formatUid(callingUid) + + " is not on whitelist"); + } + } + } try { int uid = getContext().getPackageManager().getPackageUidAsUser(packageName, userId); addPowerSaveTempWhitelistAppDirectInternal(callingUid, uid, duration, @@ -2760,13 +2762,6 @@ public class DeviceIdleController extends SystemService boolean informWhitelistChanged = false; int appId = UserHandle.getAppId(uid); synchronized (this) { - int callingAppId = UserHandle.getAppId(callingUid); - if (callingAppId >= Process.FIRST_APPLICATION_UID) { - if (!mPowerSaveWhitelistSystemAppIds.get(callingAppId)) { - throw new SecurityException("Calling app " + UserHandle.formatUid(callingUid) - + " is not on whitelist"); - } - } duration = Math.min(duration, mConstants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS); Pair<MutableLong, String> entry = mTempWhitelistAppIdEndTimes.get(appId); final boolean newEntry = entry == null; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java index 34ba753b3daa..862d8b7cac50 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java @@ -25,7 +25,9 @@ import com.android.server.job.controllers.JobStatus; public interface JobCompletedListener { /** * Callback for when a job is completed. + * + * @param stopReason The stop reason provided to JobParameters. * @param needsReschedule Whether the implementing class should reschedule this job. */ - void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule); + void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index e8e2c27f1554..b958c3f694bf 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -25,6 +25,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.UserSwitchObserver; import android.app.job.JobInfo; +import android.app.job.JobParameters; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -73,40 +74,79 @@ class JobConcurrencyManager { CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; - // Try to give higher priority types lower values. + /** + * Set of possible execution types that a job can have. The actual type(s) of a job are based + * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before + * execution (when we're trying to determine which jobs to run next) and won't change after the + * job has started executing. + * + * Try to give higher priority types lower values. + * + * @see #getJobWorkTypes(JobStatus) + */ + + /** Job shouldn't run or qualify as any other work type. */ static final int WORK_TYPE_NONE = 0; + /** The job is for an app in the TOP state for a currently active user. */ static final int WORK_TYPE_TOP = 1 << 0; - static final int WORK_TYPE_EJ = 1 << 1; - static final int WORK_TYPE_BG = 1 << 2; - static final int WORK_TYPE_BGUSER = 1 << 3; + /** + * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher + * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user. + */ + static final int WORK_TYPE_FGS = 1 << 1; + /** The job is allowed to run as an expedited job for a currently active user. */ + static final int WORK_TYPE_EJ = 1 << 2; + /** + * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, + * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so + * can run as a background job. + */ + static final int WORK_TYPE_BG = 1 << 3; + /** + * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher + * state, or is allowed to run as an expedited job, but is for a completely background user. + */ + static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4; + /** + * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, + * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user, + * so can run as a background user job. + */ + static final int WORK_TYPE_BGUSER = 1 << 5; @VisibleForTesting - static final int NUM_WORK_TYPES = 4; - private static final int ALL_WORK_TYPES = - WORK_TYPE_TOP | WORK_TYPE_EJ | WORK_TYPE_BG | WORK_TYPE_BGUSER; + static final int NUM_WORK_TYPES = 6; + private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1; @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = { WORK_TYPE_NONE, WORK_TYPE_TOP, + WORK_TYPE_FGS, WORK_TYPE_EJ, WORK_TYPE_BG, + WORK_TYPE_BGUSER_IMPORTANT, WORK_TYPE_BGUSER }) @Retention(RetentionPolicy.SOURCE) public @interface WorkType { } - private static String workTypeToString(@WorkType int workType) { + @VisibleForTesting + static String workTypeToString(@WorkType int workType) { switch (workType) { case WORK_TYPE_NONE: return "NONE"; case WORK_TYPE_TOP: return "TOP"; + case WORK_TYPE_FGS: + return "FGS"; case WORK_TYPE_EJ: return "EJ"; case WORK_TYPE_BG: return "BG"; case WORK_TYPE_BGUSER: return "BGUSER"; + case WORK_TYPE_BGUSER_IMPORTANT: + return "BGUSER_IMPORTANT"; default: return "WORK(" + workType + ")"; } @@ -131,60 +171,82 @@ class JobConcurrencyManager { new WorkConfigLimitsPerMemoryTrimLevel( new WorkTypeConfig("screen_on_normal", 11, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4)) + List.of(Pair.create(WORK_TYPE_BG, 6), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2), + Pair.create(WORK_TYPE_BGUSER, 3)) ), new WorkTypeConfig("screen_on_moderate", 9, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)) + List.of(Pair.create(WORK_TYPE_BG, 4), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), + Pair.create(WORK_TYPE_BGUSER, 1)) ), new WorkTypeConfig("screen_on_low", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1), - Pair.create(WORK_TYPE_BG, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) + List.of(Pair.create(WORK_TYPE_BG, 1), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), + Pair.create(WORK_TYPE_BGUSER, 1)) ), - new WorkTypeConfig("screen_on_critical", 5, + new WorkTypeConfig("screen_on_critical", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) + List.of(Pair.create(WORK_TYPE_BG, 1), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), + Pair.create(WORK_TYPE_BGUSER, 1)) ) ); private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF = new WorkConfigLimitsPerMemoryTrimLevel( - new WorkTypeConfig("screen_off_normal", 13, + new WorkTypeConfig("screen_off_normal", 15, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4)) + List.of(Pair.create(WORK_TYPE_BG, 6), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2), + Pair.create(WORK_TYPE_BGUSER, 3)) ), - new WorkTypeConfig("screen_off_moderate", 13, + new WorkTypeConfig("screen_off_moderate", 15, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)) + List.of(Pair.create(WORK_TYPE_BG, 4), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), + Pair.create(WORK_TYPE_BGUSER, 1)) ), - new WorkTypeConfig("screen_off_low", 7, + new WorkTypeConfig("screen_off_low", 9, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2), - Pair.create(WORK_TYPE_BG, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) + List.of(Pair.create(WORK_TYPE_BG, 1), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), + Pair.create(WORK_TYPE_BGUSER, 1)) ), - new WorkTypeConfig("screen_off_critical", 5, + new WorkTypeConfig("screen_off_critical", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax - List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) + List.of(Pair.create(WORK_TYPE_BG, 1), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), + Pair.create(WORK_TYPE_BGUSER, 1)) ) ); @@ -255,6 +317,8 @@ class JobConcurrencyManager { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); mContext.registerReceiver(mReceiver, filter); try { ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG); @@ -278,6 +342,20 @@ class JobConcurrencyManager { case Intent.ACTION_SCREEN_OFF: onInteractiveStateChanged(false); break; + case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: + if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) { + synchronized (mLock) { + stopLongRunningJobsLocked("deep doze"); + } + } + break; + case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED: + if (mPowerManager != null && mPowerManager.isPowerSaveMode()) { + synchronized (mLock) { + stopLongRunningJobsLocked("battery saver"); + } + } + break; } } }; @@ -436,7 +514,7 @@ class JobConcurrencyManager { // Update the priorities of jobs that aren't running, and also count the pending work types. // Do this before the following loop to hopefully reduce the cost of // shouldStopRunningJobLocked(). - updateNonRunningPriorities(pendingJobs, true); + updateNonRunningPrioritiesLocked(pendingJobs, true); for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { final JobServiceContext js = activeServices.get(i); @@ -552,7 +630,8 @@ class JobConcurrencyManager { + activeServices.get(i).getRunningJobLocked()); } // preferredUid will be set to uid of currently running job. - activeServices.get(i).preemptExecutingJobLocked(preemptReasonForContext[i]); + activeServices.get(i).cancelExecutingJobLocked( + JobParameters.REASON_PREEMPT, preemptReasonForContext[i]); preservePreferredUid = true; } else { final JobStatus pendingJob = contextIdToJobMap[i]; @@ -571,13 +650,26 @@ class JobConcurrencyManager { noteConcurrency(); } + @GuardedBy("mLock") + private void stopLongRunningJobsLocked(@NonNull String debugReason) { + for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; ++i) { + final JobServiceContext jsc = mService.mActiveServices.get(i); + final JobStatus jobStatus = jsc.getRunningJobLocked(); + + if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) { + jsc.cancelExecutingJobLocked(JobParameters.REASON_TIMEOUT, debugReason); + } + } + } + private void noteConcurrency() { mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(), // TODO: log per type instead of only TOP mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP)); } - private void updateNonRunningPriorities(@NonNull final List<JobStatus> pendingJobs, + @GuardedBy("mLock") + private void updateNonRunningPrioritiesLocked(@NonNull final List<JobStatus> pendingJobs, boolean updateCounter) { for (int i = 0; i < pendingJobs.size(); i++) { final JobStatus pending = pendingJobs.get(i); @@ -595,6 +687,7 @@ class JobConcurrencyManager { } } + @GuardedBy("mLock") private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType) { final List<StateController> controllers = mService.mControllers; @@ -614,6 +707,7 @@ class JobConcurrencyManager { } } + @GuardedBy("mLock") void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType) { mWorkCountTracker.onJobFinished(workType); @@ -622,7 +716,7 @@ class JobConcurrencyManager { if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { updateCounterConfigLocked(); // Preemption case needs special care. - updateNonRunningPriorities(pendingJobs, false); + updateNonRunningPrioritiesLocked(pendingJobs, false); JobStatus highestPriorityJob = null; int highPriWorkType = workType; @@ -693,7 +787,7 @@ class JobConcurrencyManager { } } else if (pendingJobs.size() > 0) { updateCounterConfigLocked(); - updateNonRunningPriorities(pendingJobs, false); + updateNonRunningPrioritiesLocked(pendingJobs, false); // This slot is now free and we have pending jobs. Start the highest priority job we // find. @@ -742,6 +836,7 @@ class JobConcurrencyManager { * another job to run, or if system state suggests the job should stop. */ @Nullable + @GuardedBy("mLock") String shouldStopRunningJobLocked(@NonNull JobServiceContext context) { final JobStatus js = context.getRunningJobLocked(); if (js == null) { @@ -784,17 +879,22 @@ class JobConcurrencyManager { // Only expedited jobs can replace expedited jobs. if (js.shouldTreatAsExpeditedJob()) { // Keep fg/bg user distinction. - if (workType == WORK_TYPE_BGUSER) { - // For now, let any bg user job replace a bg user expedited job. - // TODO: limit to ej once we have dedicated bg user ej slots. - if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER) > 0) { - return "blocking " + workTypeToString(workType) + " queue"; + if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) { + // Let any important bg user job replace a bg user expedited job. + if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER_IMPORTANT) > 0) { + return "blocking " + workTypeToString(WORK_TYPE_BGUSER_IMPORTANT) + " queue"; } - } else { - if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) { - return "blocking " + workTypeToString(workType) + " queue"; + // Let a fg user EJ preempt a bg user EJ (if able), but not the other way around. + if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0 + && mWorkCountTracker.canJobStart(WORK_TYPE_EJ, workType) + != WORK_TYPE_NONE) { + return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue"; } + } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) { + return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue"; } + // No other pending EJs. Return null so we don't let regular jobs preempt an EJ. + return null; } // Easy check. If there are pending jobs of the same work type, then we know that @@ -848,6 +948,7 @@ class JobConcurrencyManager { return s.toString(); } + @GuardedBy("mLock") void updateConfigLocked() { DeviceConfig.Properties properties = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER); @@ -977,10 +1078,12 @@ class JobConcurrencyManager { int getJobWorkTypes(@NonNull JobStatus js) { int classification = 0; - // TODO: create dedicated work type for FGS + if (shouldRunAsFgUserJob(js)) { if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { classification |= WORK_TYPE_TOP; + } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) { + classification |= WORK_TYPE_FGS; } else { classification |= WORK_TYPE_BG; } @@ -989,7 +1092,11 @@ class JobConcurrencyManager { classification |= WORK_TYPE_EJ; } } else { - // TODO(171305774): create dedicated slots for EJs of bg user + if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE + || js.shouldTreatAsExpeditedJob()) { + classification |= WORK_TYPE_BGUSER_IMPORTANT; + } + // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here. classification |= WORK_TYPE_BGUSER; } @@ -1001,15 +1108,21 @@ class JobConcurrencyManager { private static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_"; private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_"; + private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_"; private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_"; private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_"; private static final String KEY_PREFIX_MAX_BGUSER = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_"; + private static final String KEY_PREFIX_MAX_BGUSER_IMPORTANT = + CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_important_"; private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_"; + private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_"; private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_"; private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_"; private static final String KEY_PREFIX_MIN_BGUSER = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_"; + private static final String KEY_PREFIX_MIN_BGUSER_IMPORTANT = + CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_important_"; private final String mConfigIdentifier; private int mMaxTotal; @@ -1053,6 +1166,10 @@ class JobConcurrencyManager { properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal)))); mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop); + final int maxFgs = Math.max(1, Math.min(mMaxTotal, + properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier, + mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal)))); + mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs); final int maxEj = Math.max(1, Math.min(mMaxTotal, properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal)))); @@ -1061,6 +1178,10 @@ class JobConcurrencyManager { properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal)))); mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg); + final int maxBgUserImp = Math.max(1, Math.min(mMaxTotal, + properties.getInt(KEY_PREFIX_MAX_BGUSER_IMPORTANT + mConfigIdentifier, + mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, mMaxTotal)))); + mMaxAllowedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, maxBgUserImp); final int maxBgUser = Math.max(1, Math.min(mMaxTotal, properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal)))); @@ -1074,6 +1195,12 @@ class JobConcurrencyManager { mDefaultMinReservedSlots.get(WORK_TYPE_TOP)))); mMinReservedSlots.put(WORK_TYPE_TOP, minTop); remaining -= minTop; + // Ensure fgs is in the range [0, min(maxFgs, remaining)] + final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining), + properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier, + mDefaultMinReservedSlots.get(WORK_TYPE_FGS)))); + mMinReservedSlots.put(WORK_TYPE_FGS, minFgs); + remaining -= minFgs; // Ensure ej is in the range [0, min(maxEj, remaining)] final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining), properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier, @@ -1086,6 +1213,11 @@ class JobConcurrencyManager { mDefaultMinReservedSlots.get(WORK_TYPE_BG)))); mMinReservedSlots.put(WORK_TYPE_BG, minBg); remaining -= minBg; + // Ensure bg user imp is in the range [0, min(maxBgUserImp, remaining)] + final int minBgUserImp = Math.max(0, Math.min(Math.min(maxBgUserImp, remaining), + properties.getInt(KEY_PREFIX_MIN_BGUSER_IMPORTANT + mConfigIdentifier, + mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, 0)))); + mMinReservedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, minBgUserImp); // Ensure bg user is in the range [0, min(maxBgUser, remaining)] final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining), properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, @@ -1111,6 +1243,10 @@ class JobConcurrencyManager { .println(); pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP)) .println(); + pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS)) + .println(); + pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS)) + .println(); pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ)) .println(); pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ)) @@ -1120,6 +1256,10 @@ class JobConcurrencyManager { pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG)) .println(); pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, + mMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println(); + pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, + mMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println(); + pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BGUSER)).println(); pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println(); @@ -1205,6 +1345,7 @@ class JobConcurrencyManager { private int mConfigMaxTotal; private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES); private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES); /** * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't @@ -1218,16 +1359,10 @@ class JobConcurrencyManager { void setConfig(@NonNull WorkTypeConfig workTypeConfig) { mConfigMaxTotal = workTypeConfig.getMaxTotal(); - mConfigNumReservedSlots.put(WORK_TYPE_TOP, - workTypeConfig.getMinReserved(WORK_TYPE_TOP)); - mConfigNumReservedSlots.put(WORK_TYPE_EJ, workTypeConfig.getMinReserved(WORK_TYPE_EJ)); - mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG)); - mConfigNumReservedSlots.put(WORK_TYPE_BGUSER, - workTypeConfig.getMinReserved(WORK_TYPE_BGUSER)); - mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP)); - mConfigAbsoluteMaxSlots.put(WORK_TYPE_EJ, workTypeConfig.getMax(WORK_TYPE_EJ)); - mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG)); - mConfigAbsoluteMaxSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMax(WORK_TYPE_BGUSER)); + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + mConfigNumReservedSlots.put(workType, workTypeConfig.getMinReserved(workType)); + mConfigAbsoluteMaxSlots.put(workType, workTypeConfig.getMax(workType)); + } mNumUnspecializedRemaining = mConfigMaxTotal; for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) { @@ -1260,15 +1395,10 @@ class JobConcurrencyManager { // We don't need to adjust reservations if only one work type was modified // because that work type is the one we're using. - // 0 is WORK_TYPE_NONE. - int workType = 1; - int rem = workTypes; - while (rem > 0) { - if ((rem & 1) != 0) { + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workType & workTypes) == workType) { maybeAdjustReservations(workType); } - rem = rem >>> 1; - workType = workType << 1; } } } @@ -1280,21 +1410,11 @@ class JobConcurrencyManager { int numAdj = 0; // We don't know which type we'll classify the job as when we run it yet, so make sure // we have space in all applicable slots. - if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) { - mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) { - mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { - mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) { - mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj); - numAdj++; + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workTypes & workType) == workType) { + mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj); + numAdj++; + } } return numAdj; @@ -1388,105 +1508,45 @@ class JobConcurrencyManager { mNumUnspecializedRemaining = mConfigMaxTotal; // Step 1 - int runTop = mNumRunningJobs.get(WORK_TYPE_TOP); - int resTop = runTop; - mNumUnspecializedRemaining -= resTop; - int runEj = mNumRunningJobs.get(WORK_TYPE_EJ); - int resEj = runEj; - mNumUnspecializedRemaining -= resEj; - int runBg = mNumRunningJobs.get(WORK_TYPE_BG); - int resBg = runBg; - mNumUnspecializedRemaining -= resBg; - int runBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER); - int resBgUser = runBgUser; - mNumUnspecializedRemaining -= resBgUser; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int run = mNumRunningJobs.get(workType); + mRecycledReserved.put(workType, run); + mNumUnspecializedRemaining -= run; + } // Step 2 - final int numTop = runTop + mNumPendingJobs.get(WORK_TYPE_TOP); - int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numTop, mConfigNumReservedSlots.get(WORK_TYPE_TOP) - resTop))); - resTop += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numEj = runEj + mNumPendingJobs.get(WORK_TYPE_EJ); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numEj, mConfigNumReservedSlots.get(WORK_TYPE_EJ) - resEj))); - resEj += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numBg = runBg + mNumPendingJobs.get(WORK_TYPE_BG); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numBg, mConfigNumReservedSlots.get(WORK_TYPE_BG) - resBg))); - resBg += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numBgUser = runBgUser + mNumPendingJobs.get(WORK_TYPE_BGUSER); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numBgUser, - mConfigNumReservedSlots.get(WORK_TYPE_BGUSER) - resBgUser))); - resBgUser += fillUp; - mNumUnspecializedRemaining -= fillUp; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); + int res = mRecycledReserved.get(workType); + int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, + Math.min(num, mConfigNumReservedSlots.get(workType) - res))); + res += fillUp; + mRecycledReserved.put(workType, res); + mNumUnspecializedRemaining -= fillUp; + } // Step 3 - int unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop)); - mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), numEj) - resEj)); - mNumActuallyReservedSlots.put(WORK_TYPE_EJ, resEj + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg)); - mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser) - - resBgUser)); - mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); + int res = mRecycledReserved.get(workType); + int unspecializedAssigned = Math.max(0, + Math.min(mNumUnspecializedRemaining, + Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res)); + mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned); + mNumUnspecializedRemaining -= unspecializedAssigned; + } } int canJobStart(int workTypes) { - if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), - mNumActuallyReservedSlots.get(WORK_TYPE_TOP) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_TOP) + mNumStartingJobs.get(WORK_TYPE_TOP) - < maxAllowed) { - return WORK_TYPE_TOP; - } - } - if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), - mNumActuallyReservedSlots.get(WORK_TYPE_EJ) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_EJ) + mNumStartingJobs.get(WORK_TYPE_EJ) - < maxAllowed) { - return WORK_TYPE_EJ; - } - } - if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), - mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG) - < maxAllowed) { - return WORK_TYPE_BG; - } - } - if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), - mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER) - + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER) - < maxAllowed) { - return WORK_TYPE_BGUSER; + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workTypes & workType) == workType) { + final int maxAllowed = Math.min( + mConfigAbsoluteMaxSlots.get(workType), + mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining); + if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) + < maxAllowed) { + return workType; + } } } return WORK_TYPE_NONE; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 8bb03e911528..515cb747a99e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1745,11 +1745,12 @@ public class JobSchedulerService extends com.android.server.SystemService * A job just finished executing. We fetch the * {@link com.android.server.job.controllers.JobStatus} from the store and depending on * whether we want to reschedule we re-add it to the controllers. - * @param jobStatus Completed job. + * + * @param jobStatus Completed job. * @param needsReschedule Whether the implementing class should reschedule this job. */ @Override - public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) { + public void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule) { if (DEBUG) { Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); } @@ -1767,6 +1768,11 @@ public class JobSchedulerService extends com.android.server.SystemService // we stop it. final JobStatus rescheduledJob = needsReschedule ? getRescheduleJobForFailureLocked(jobStatus) : null; + if (rescheduledJob != null + && (stopReason == JobParameters.REASON_TIMEOUT + || stopReason == JobParameters.REASON_PREEMPT)) { + rescheduledJob.disallowRunInBatterySaverAndDoze(); + } // Do not write back immediately if this is a periodic job. The job may get lost if system // shuts down before it is added back. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 2a23d60d8af6..6802b6b6ee2d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -269,14 +269,14 @@ public final class JobServiceContext implements ServiceConnection { try { final int bindFlags; if (job.shouldTreatAsExpeditedJob()) { - // TODO(171305774): The job should run on the little cores. We'll probably need - // another binding flag for that. bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_ALMOST_PERCEPTIBLE - | Context.BIND_ALLOW_NETWORK_ACCESS; + | Context.BIND_ALLOW_NETWORK_ACCESS + | Context.BIND_NOT_APP_COMPONENT_USAGE; } else { bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND - | Context.BIND_NOT_PERCEPTIBLE; + | Context.BIND_NOT_PERCEPTIBLE + | Context.BIND_NOT_APP_COMPONENT_USAGE; } binding = mContext.bindServiceAsUser(intent, this, bindFlags, UserHandle.of(job.getUserId())); @@ -353,15 +353,10 @@ public final class JobServiceContext implements ServiceConnection { /** Called externally when a job that was scheduled for execution should be cancelled. */ @GuardedBy("mLock") - void cancelExecutingJobLocked(int reason, String debugReason) { + void cancelExecutingJobLocked(int reason, @NonNull String debugReason) { doCancelLocked(reason, debugReason); } - @GuardedBy("mLock") - void preemptExecutingJobLocked(@NonNull String reason) { - doCancelLocked(JobParameters.REASON_PREEMPT, reason); - } - int getPreferredUid() { return mPreferredUid; } @@ -379,8 +374,8 @@ public final class JobServiceContext implements ServiceConnection { } boolean isWithinExecutionGuaranteeTime() { - return mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - < sElapsedRealtimeClock.millis(); + return sElapsedRealtimeClock.millis() + < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis; } @GuardedBy("mLock") @@ -618,7 +613,7 @@ public final class JobServiceContext implements ServiceConnection { } @GuardedBy("mLock") - private void doCancelLocked(int stopReasonCode, String debugReason) { + private void doCancelLocked(int stopReasonCode, @Nullable String debugReason) { if (mVerb == VERB_FINISHED) { if (DEBUG) { Slog.d(TAG, @@ -731,7 +726,7 @@ public final class JobServiceContext implements ServiceConnection { * _ENDING -> No point in doing anything here, so we ignore. */ @GuardedBy("mLock") - private void handleCancelLocked(String reason) { + private void handleCancelLocked(@Nullable String reason) { if (JobSchedulerService.DEBUG) { Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " " + VERB_STRINGS[mVerb]); @@ -815,7 +810,7 @@ public final class JobServiceContext implements ServiceConnection { * VERB_STOPPING. */ @GuardedBy("mLock") - private void sendStopMessageLocked(String reason) { + private void sendStopMessageLocked(@Nullable String reason) { removeOpTimeOutLocked(); if (mVerb != VERB_EXECUTING) { Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob); @@ -841,18 +836,19 @@ public final class JobServiceContext implements ServiceConnection { * we want to clean up internally. */ @GuardedBy("mLock") - private void closeAndCleanupJobLocked(boolean reschedule, String reason) { + private void closeAndCleanupJobLocked(boolean reschedule, @Nullable String reason) { final JobStatus completedJob; if (mVerb == VERB_FINISHED) { return; } applyStoppedReasonLocked(reason); completedJob = mRunningJob; - mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason); + final int stopReason = mParams.getStopReason(); + mJobPackageTracker.noteInactive(completedJob, stopReason, reason); FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, completedJob.getSourceUid(), null, completedJob.getBatteryName(), FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED, - mParams.getStopReason(), completedJob.getStandbyBucket(), completedJob.getJobId(), + stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(), completedJob.hasChargingConstraint(), completedJob.hasBatteryNotLowConstraint(), completedJob.hasStorageNotLowConstraint(), @@ -863,7 +859,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.hasContentTriggerConstraint()); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), - mParams.getStopReason()); + stopReason); } catch (RemoteException e) { // Whatever. } @@ -882,11 +878,11 @@ public final class JobServiceContext implements ServiceConnection { service = null; mAvailable = true; removeOpTimeOutLocked(); - mCompletedListener.onJobCompletedLocked(completedJob, reschedule); + mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); } - private void applyStoppedReasonLocked(String reason) { + private void applyStoppedReasonLocked(@Nullable String reason) { if (reason != null && mStoppedReason == null) { mStoppedReason = reason; mStoppedTime = sElapsedRealtimeClock.millis(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index eaf8f4d96331..aa8d98c01853 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -954,7 +954,7 @@ public final class JobStore { appBucket, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime, - (rtcIsGood) ? null : rtcRuntimes, internalFlags); + (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0); return js; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index 192f5e66255d..79ef321eaf07 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -64,7 +64,7 @@ public final class DeviceIdleJobsController extends StateController { * when the app is temp whitelisted or in the foreground. */ private final ArraySet<JobStatus> mAllowInIdleJobs; - private final SparseBooleanArray mForegroundUids; + private final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor; private final DeviceIdleJobsDelayHandler mHandler; private final PowerManager mPowerManager; @@ -77,7 +77,6 @@ public final class DeviceIdleJobsController extends StateController { private int[] mDeviceIdleWhitelistAppIds; private int[] mPowerSaveTempWhitelistAppIds; - // onReceive private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -120,6 +119,10 @@ public final class DeviceIdleJobsController extends StateController { } }; + /** Criteria for whether or not we should a job's rush evaluation when the device exits Doze. */ + private final Predicate<JobStatus> mShouldRushEvaluation = (jobStatus) -> + jobStatus.isRequestedExpeditedJob() || mForegroundUids.get(jobStatus.getSourceUid()); + public DeviceIdleJobsController(JobSchedulerService service) { super(service); @@ -133,7 +136,6 @@ public final class DeviceIdleJobsController extends StateController { mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); mAllowInIdleJobs = new ArraySet<>(); - mForegroundUids = new SparseBooleanArray(); final IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); @@ -156,14 +158,9 @@ public final class DeviceIdleJobsController extends StateController { mHandler.removeMessages(PROCESS_BACKGROUND_JOBS); mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); } else { - // When coming out of doze, process all foreground uids immediately, while others - // will be processed after a delay of 3 seconds. - for (int i = 0; i < mForegroundUids.size(); i++) { - if (mForegroundUids.valueAt(i)) { - mService.getJobStore().forEachJobForSourceUid( - mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor); - } - } + // When coming out of doze, process all foreground uids and EJs immediately, + // while others will be processed after a delay of 3 seconds. + mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor); mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 5bdeb38a1424..bad8dc1ad1cb 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -91,6 +91,12 @@ public final class JobStatus { static final int CONSTRAINT_WITHIN_EXPEDITED_QUOTA = 1 << 23; // Implicit constraint static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint + // The following set of dynamic constraints are for specific use cases (as explained in their + // relative naming and comments). Right now, they apply different constraints, which is fine, + // but if in the future, we have overlapping dynamic constraint sets, removing one constraint + // set may accidentally remove a constraint applied by another dynamic set. + // TODO: properly handle overlapping dynamic constraint sets + /** * The additional set of dynamic constraints that must be met if the job's effective bucket is * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't @@ -103,6 +109,13 @@ public final class JobStatus { | CONSTRAINT_IDLE; /** + * The additional set of dynamic constraints that must be met if this is an expedited job that + * had a long enough run while the device was Dozing or in battery saver. + */ + private static final int DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS = + CONSTRAINT_DEVICE_NOT_DOZING | CONSTRAINT_BACKGROUND_NOT_RESTRICTED; + + /** * Standard media URIs that contain the media files that might be important to the user. * @see #mHasMediaBackupExemption */ @@ -426,7 +439,8 @@ public final class JobStatus { private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, int standbyBucket, String tag, int numFailures, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, - long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) { + long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags, + int dynamicConstraints) { this.job = job; this.callingUid = callingUid; this.standbyBucket = standbyBucket; @@ -487,6 +501,7 @@ public final class JobStatus { } this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; + addDynamicConstraints(dynamicConstraints); mReadyNotDozing = canRunInDoze(); if (standbyBucket == RESTRICTED_INDEX) { addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); @@ -521,7 +536,7 @@ public final class JobStatus { jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(), - jobStatus.getInternalFlags()); + jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints); mPersistedUtcTimes = jobStatus.mPersistedUtcTimes; if (jobStatus.mPersistedUtcTimes != null) { if (DEBUG) { @@ -543,12 +558,12 @@ public final class JobStatus { long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, Pair<Long, Long> persistedExecutionTimesUTC, - int innerFlags) { + int innerFlags, int dynamicConstraints) { this(job, callingUid, sourcePkgName, sourceUserId, standbyBucket, sourceTag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, - lastSuccessfulRunTime, lastFailedRunTime, innerFlags); + lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints); // Only during initial inflation do we record the UTC-timebase execution bounds // read from the persistent store. If we ever have to recreate the JobStatus on @@ -572,7 +587,8 @@ public final class JobStatus { rescheduling.getStandbyBucket(), rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis, - lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags()); + lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(), + rescheduling.mDynamicConstraints); } /** @@ -609,7 +625,7 @@ public final class JobStatus { standbyBucket, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, - /*innerFlags=*/ 0); + /*innerFlags=*/ 0, /* dynamicConstraints */ 0); } public void enqueueWorkLocked(JobWorkItem work) { @@ -1083,12 +1099,15 @@ public final class JobStatus { * in Doze. */ public boolean canRunInDoze() { - return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob(); + return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 + || (shouldTreatAsExpeditedJob() + && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0); } boolean canRunInBatterySaver() { return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0 - || shouldTreatAsExpeditedJob(); + || (shouldTreatAsExpeditedJob() + && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0); } boolean shouldIgnoreNetworkBlocking() { @@ -1245,6 +1264,14 @@ public final class JobStatus { } /** + * Add additional constraints to prevent this job from running when doze or battery saver are + * active. + */ + public void disallowRunInBatterySaverAndDoze() { + addDynamicConstraints(DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS); + } + + /** * Indicates that this job cannot run without the specified constraints. This is evaluated * separately from the job's explicitly requested constraints and MUST be satisfied before * the job can run if the app doesn't have quota. diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index b18a22b408f5..1d6f20dd4b27 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -47,6 +47,7 @@ java_library { static_libs: [ "exoplayer2-extractor", "mediatranscoding_aidl_interface-java", + "modules-annotation-minsdk", "modules-utils-build", ], jarjar_rules: "jarjar_rules.txt", @@ -108,7 +109,7 @@ filegroup { filegroup { name: "mediaparser-srcs", srcs: [ - "java/android/media/MediaParser.java" + "java/android/media/MediaParser.java", ], path: "java", } diff --git a/apex/media/framework/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt index eb71fddc05cb..91489dcee0a1 100644 --- a/apex/media/framework/jarjar_rules.txt +++ b/apex/media/framework/jarjar_rules.txt @@ -1,2 +1,2 @@ -rule com.android.modules.utils.** android.media.internal.utils.@1 +rule com.android.modules.** android.media.internal.@1 rule com.google.android.exoplayer2.** android.media.internal.exo.@1 diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java index 685cf0dc7f77..906071fe4c7b 100644 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java @@ -39,10 +39,12 @@ import java.util.Set; for handling newer video codec format and media features. <p> - Android 12 introduces seamless media transcoding feature. By default, Android assumes apps can - support playback of all media formats. Apps that would like to request that media be transcoded - into a more compatible format should declare their media capabilities in a media_capabilities - .xml resource file and add it as a property tag in the AndroidManifest.xml file. Here is a example: + 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>. By default, Android assumes apps can support playback of all + media formats. Apps that would like to request that media be transcoded into a more compatible + format should declare their media capabilities in a media_capabilities.xml resource file and add it + as a property tag in the AndroidManifest.xml file. Here is a example: <pre> {@code <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java index 9ec25fe48a2e..f39bcfb267bf 100644 --- a/apex/media/framework/java/android/media/MediaCommunicationManager.java +++ b/apex/media/framework/java/android/media/MediaCommunicationManager.java @@ -27,12 +27,14 @@ import android.annotation.SystemService; import android.content.Context; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; +import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.service.media.MediaBrowserService; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.modules.annotation.MinSdk; import com.android.modules.utils.build.SdkLevel; import java.util.Collections; @@ -45,6 +47,7 @@ import java.util.concurrent.Executor; * Provides support for interacting with {@link android.media.MediaSession2 MediaSession2s} * that applications have published to express their ongoing media playback state. */ +@MinSdk(Build.VERSION_CODES.S) @SystemService(Context.MEDIA_COMMUNICATION_SERVICE) public class MediaCommunicationManager { private static final String TAG = "MediaCommunicationManager"; diff --git a/apex/media/framework/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java index 6397ba3996f3..7697359e7caf 100644 --- a/apex/media/framework/java/android/media/MediaSession2.java +++ b/apex/media/framework/java/android/media/MediaSession2.java @@ -32,6 +32,7 @@ import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.BadParcelableException; import android.os.Bundle; @@ -43,6 +44,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import com.android.modules.utils.build.SdkLevel; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -86,6 +89,7 @@ public class MediaSession2 implements AutoCloseable { private final String mSessionId; private final PendingIntent mSessionActivity; private final Session2Token mSessionToken; + private final MediaSessionManager mMediaSessionManager; private final MediaCommunicationManager mCommunicationManager; private final Handler mResultHandler; @@ -114,7 +118,13 @@ public class MediaSession2 implements AutoCloseable { mSessionStub = new Session2Link(this); mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), mSessionStub, tokenExtras); - mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class); + if (SdkLevel.isAtLeastS()) { + mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class); + mMediaSessionManager = null; + } else { + mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); + mCommunicationManager = null; + } // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. mResultHandler = new Handler(context.getMainLooper()); mClosed = false; @@ -315,6 +325,14 @@ public class MediaSession2 implements AutoCloseable { return mCallback; } + boolean isTrustedForMediaControl(RemoteUserInfo remoteUserInfo) { + if (SdkLevel.isAtLeastS()) { + return mCommunicationManager.isTrustedForMediaControl(remoteUserInfo); + } else { + return mMediaSessionManager.isTrustedForMediaControl(remoteUserInfo); + } + } + void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { synchronized (mLock) { if (mForegroundServiceEventCallback == callback) { @@ -350,7 +368,7 @@ public class MediaSession2 implements AutoCloseable { final ControllerInfo controllerInfo = new ControllerInfo( remoteUserInfo, - mCommunicationManager.isTrustedForMediaControl(remoteUserInfo), + isTrustedForMediaControl(remoteUserInfo), controller, connectionHints); mCallbackExecutor.execute(() -> { @@ -606,9 +624,15 @@ public class MediaSession2 implements AutoCloseable { // Notify framework about the newly create session after the constructor is finished. // Otherwise, framework may access the session before the initialization is finished. try { - MediaCommunicationManager manager = - mContext.getSystemService(MediaCommunicationManager.class); - manager.notifySession2Created(session2.getToken()); + if (SdkLevel.isAtLeastS()) { + MediaCommunicationManager manager = + mContext.getSystemService(MediaCommunicationManager.class); + manager.notifySession2Created(session2.getToken()); + } else { + MediaSessionManager manager = + mContext.getSystemService(MediaSessionManager.class); + manager.notifySession2Created(session2.getToken()); + } } catch (Exception e) { session2.close(); throw e; diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index f85e30d38f86..9c044b5e632e 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -66,6 +66,7 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT = "set-phone-acct-suggestion-component"; private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account"; + private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service"; private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer"; private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression"; @@ -112,6 +113,7 @@ public final class Telecom extends BaseCommand { + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>" + " <LABEL> <ADDRESS>\n" + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n" + + "usage: telecom set-call-diagnostic-service <PACKAGE>\n" + "usage: telecom set-default-dialer <PACKAGE>\n" + "usage: telecom get-default-dialer\n" + "usage: telecom get-system-dialer\n" @@ -131,6 +133,7 @@ public final class Telecom extends BaseCommand { + "telecom set-phone-account-disabled: Disables the given phone account, if it" + " has already been registered with telecom.\n" + "\n" + + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n" + "telecom set-default-dialer: Sets the override default dialer to the given" + " component; this will override whatever the dialer role is set to.\n" + "\n" @@ -206,6 +209,9 @@ public final class Telecom extends BaseCommand { case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT: runSetTestPhoneAcctSuggestionComponent(); break; + case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE: + runSetCallDiagnosticService(); + break; case COMMAND_REGISTER_SIM_PHONE_ACCOUNT: runRegisterSimPhoneAccount(); break; @@ -323,6 +329,13 @@ public final class Telecom extends BaseCommand { mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool); } + private void runSetCallDiagnosticService() throws RemoteException { + String packageName = nextArg(); + if ("default".equals(packageName)) packageName = null; + mTelecomService.setTestCallDiagnosticService(packageName); + System.out.println("Success - " + packageName + " set as call diagnostic service."); + } + private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException { final String componentName = nextArg(); mTelecomService.setTestPhoneAcctSuggestionComponent(componentName); diff --git a/core/api/current.txt b/core/api/current.txt index 9b4b3cb57430..310e95990b85 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1072,6 +1072,7 @@ package android { field public static final int panelTextAppearance = 16842850; // 0x1010062 field public static final int parentActivityName = 16843687; // 0x10103a7 field @Deprecated public static final int password = 16843100; // 0x101015c + field public static final int passwordsActivity = 16844351; // 0x101063f field public static final int path = 16842794; // 0x101002a field public static final int pathAdvancedPattern = 16844320; // 0x1010620 field public static final int pathData = 16843781; // 0x1010405 @@ -6810,14 +6811,18 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(@NonNull android.graphics.Color, @Nullable android.graphics.Color, @Nullable android.graphics.Color); + ctor public WallpaperColors(@NonNull android.graphics.Color, @Nullable android.graphics.Color, @Nullable android.graphics.Color, int); method public int describeContents(); method public static android.app.WallpaperColors fromBitmap(@NonNull android.graphics.Bitmap); method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable); + method public int getColorHints(); method @NonNull public android.graphics.Color getPrimaryColor(); method @Nullable public android.graphics.Color getSecondaryColor(); method @Nullable public android.graphics.Color getTertiaryColor(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 + field public static final int HINT_SUPPORTS_DARK_THEME = 2; // 0x2 } public final class WallpaperInfo implements android.os.Parcelable { @@ -8367,7 +8372,6 @@ package android.appwidget { public class AppWidgetHostView extends android.widget.FrameLayout { ctor public AppWidgetHostView(android.content.Context); ctor public AppWidgetHostView(android.content.Context, int, int); - method public void clearCurrentSize(); method public int getAppWidgetId(); method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(); method public static android.graphics.Rect getDefaultPaddingForWidget(android.content.Context, android.content.ComponentName, android.graphics.Rect); @@ -8377,13 +8381,12 @@ package android.appwidget { method public void resetColorResources(); method public void setAppWidget(int, android.appwidget.AppWidgetProviderInfo); method public void setColorResources(@NonNull android.util.SparseIntArray); - method public void setCurrentSize(@NonNull android.graphics.PointF); method public void setExecutor(java.util.concurrent.Executor); method public void setOnLightBackground(boolean); method public void updateAppWidget(android.widget.RemoteViews); method public void updateAppWidgetOptions(android.os.Bundle); method @Deprecated public void updateAppWidgetSize(android.os.Bundle, int, int, int, int); - method public void updateAppWidgetSize(@NonNull android.os.Bundle, @NonNull java.util.List<android.graphics.PointF>); + method public void updateAppWidgetSize(@NonNull android.os.Bundle, @NonNull java.util.List<android.util.SizeF>); } public class AppWidgetManager { @@ -9712,7 +9715,7 @@ package android.companion { } public final class CompanionDeviceManager { - method public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler); + method @RequiresPermission(value=android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler); method public void disassociate(@NonNull String); method @NonNull public java.util.List<java.lang.String> getAssociations(); method public boolean hasNotificationAccess(android.content.ComponentName); @@ -10322,6 +10325,7 @@ package android.content { method @Deprecated public abstract void clearWallpaper() throws java.io.IOException; method @NonNull public android.content.Context createAttributionContext(@Nullable String); method public abstract android.content.Context createConfigurationContext(@NonNull android.content.res.Configuration); + method @NonNull public android.content.Context createContext(@NonNull android.content.ContextParams); method public abstract android.content.Context createContextForSplit(String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.Context createDeviceProtectedStorageContext(); method public abstract android.content.Context createDisplayContext(@NonNull android.view.Display); @@ -10543,6 +10547,19 @@ package android.content { field public static final String WINDOW_SERVICE = "window"; } + public final class ContextParams { + method @Nullable public String getAttributionTag(); + method @Nullable public String getReceiverAttributionTag(); + method @Nullable public String getReceiverPackage(); + } + + public static final class ContextParams.Builder { + ctor public ContextParams.Builder(); + method @NonNull public android.content.ContextParams build(); + method @NonNull public android.content.ContextParams.Builder setAttributionTag(@NonNull String); + method @NonNull public android.content.ContextParams.Builder setReceiverPackage(@NonNull String, @Nullable String); + } + public class ContextWrapper extends android.content.Context { ctor public ContextWrapper(android.content.Context); method protected void attachBaseContext(android.content.Context); @@ -11764,6 +11781,7 @@ package android.content.pm { method public boolean isResourceOverlay(); method public boolean isVirtualPreload(); method public CharSequence loadDescription(android.content.pm.PackageManager); + field public static final int CATEGORY_ACCESSIBILITY = 8; // 0x8 field public static final int CATEGORY_AUDIO = 1; // 0x1 field public static final int CATEGORY_GAME = 0; // 0x0 field public static final int CATEGORY_IMAGE = 3; // 0x3 @@ -12877,18 +12895,18 @@ package android.content.pm { } public class ShortcutManager { - method public boolean addDynamicShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>); - method public android.content.Intent createShortcutResultIntent(@NonNull android.content.pm.ShortcutInfo); + method @WorkerThread public boolean addDynamicShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>); + method @WorkerThread public android.content.Intent createShortcutResultIntent(@NonNull android.content.pm.ShortcutInfo); method public void disableShortcuts(@NonNull java.util.List<java.lang.String>); method public void disableShortcuts(@NonNull java.util.List<java.lang.String>, CharSequence); method public void enableShortcuts(@NonNull java.util.List<java.lang.String>); - method @NonNull public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); + method @NonNull @WorkerThread public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); method public int getIconMaxHeight(); method public int getIconMaxWidth(); - method @NonNull public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts(); + method @NonNull @WorkerThread public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts(); method public int getMaxShortcutCountPerActivity(); - method @NonNull public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); - method @NonNull public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(int); + method @NonNull @WorkerThread public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); + method @NonNull @WorkerThread public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(int); method public boolean isRateLimitingActive(); method public boolean isRequestPinShortcutSupported(); method public void pushDynamicShortcut(@NonNull android.content.pm.ShortcutInfo); @@ -12896,10 +12914,10 @@ package android.content.pm { method public void removeDynamicShortcuts(@NonNull java.util.List<java.lang.String>); method public void removeLongLivedShortcuts(@NonNull java.util.List<java.lang.String>); method public void reportShortcutUsed(String); - method public boolean requestPinShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.content.IntentSender); - method public boolean setDynamicShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>); + method @WorkerThread public boolean requestPinShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.content.IntentSender); + method @WorkerThread public boolean setDynamicShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>); method public void updateShortcutVisibility(@NonNull String, @Nullable byte[], boolean); - method public boolean updateShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>); + method @WorkerThread public boolean updateShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>); field public static final int FLAG_MATCH_CACHED = 8; // 0x8 field public static final int FLAG_MATCH_DYNAMIC = 2; // 0x2 field public static final int FLAG_MATCH_MANIFEST = 1; // 0x1 @@ -12943,6 +12961,27 @@ package android.content.pm { } +package android.content.pm.verify.domain { + + public final class DomainVerificationManager { + method @Nullable public android.content.pm.verify.domain.DomainVerificationUserState getDomainVerificationUserState(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + } + + public final class DomainVerificationUserState implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); + method @NonNull public String getPackageName(); + method @NonNull public android.os.UserHandle getUser(); + method @NonNull public boolean isLinkHandlingAllowed(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserState> CREATOR; + field public static final int DOMAIN_STATE_NONE = 0; // 0x0 + field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 + field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 + } + +} + package android.content.res { public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable { @@ -17531,6 +17570,9 @@ package android.hardware.biometrics { public class BiometricManager { method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(); method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int); + method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getButtonLabel(int); + method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getPromptMessage(int); + method @Nullable @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public CharSequence getSettingName(int); field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1 field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc @@ -17742,6 +17784,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SENSOR_AVAILABLE_TEST_PATTERN_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN; @@ -18346,9 +18389,20 @@ package android.hardware.camera2 { field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100 } + public class MultiResolutionImageReader implements java.lang.AutoCloseable { + 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); + } + public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult { method @NonNull public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults(); - method public java.util.Map<java.lang.String,android.hardware.camera2.CaptureResult> getPhysicalCameraResults(); + method @Deprecated public java.util.Map<java.lang.String,android.hardware.camera2.CaptureResult> getPhysicalCameraResults(); + method @NonNull public java.util.Map<java.lang.String,android.hardware.camera2.TotalCaptureResult> getPhysicalCameraTotalResults(); } } @@ -18397,9 +18451,11 @@ package android.hardware.camera2.params { public final class InputConfiguration { ctor public InputConfiguration(int, int, int); + ctor public InputConfiguration(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int); method public int getFormat(); method public int getHeight(); method public int getWidth(); + method public boolean isMultiResolution(); } public final class LensShadingMap { @@ -18442,6 +18498,20 @@ package android.hardware.camera2.params { field public static final int METERING_WEIGHT_MIN = 0; // 0x0 } + public final class MultiResolutionStreamConfigurationMap { + method @NonNull public int[] getInputFormats(); + method @NonNull public java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo> getInputInfo(int); + method @NonNull public int[] getOutputFormats(); + method @NonNull public java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo> getOutputInfo(int); + } + + public class MultiResolutionStreamInfo { + ctor public MultiResolutionStreamInfo(int, int, @NonNull String); + method public int getHeight(); + method @NonNull public String getPhysicalCameraId(); + method public int getWidth(); + } + public final class OisSample { ctor public OisSample(long, float, float); method public long getTimestamp(); @@ -18454,6 +18524,7 @@ package android.hardware.camera2.params { ctor public OutputConfiguration(int, @NonNull android.view.Surface); ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>); method public void addSurface(@NonNull android.view.Surface); + method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader); method public int describeContents(); method public void enableSurfaceSharing(); method public int getMaxSharedSurfaceCount(); @@ -18565,6 +18636,23 @@ package android.hardware.camera2.params { package android.hardware.display { + public final class DeviceProductInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getConnectionToSinkType(); + method public int getManufactureWeek(); + method public int getManufactureYear(); + method @NonNull public String getManufacturerPnpId(); + method public int getModelYear(); + method @Nullable public String getName(); + method @NonNull public String getProductId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CONNECTION_TO_SINK_BUILT_IN = 1; // 0x1 + field public static final int CONNECTION_TO_SINK_DIRECT = 2; // 0x2 + field public static final int CONNECTION_TO_SINK_TRANSITIVE = 3; // 0x3 + field public static final int CONNECTION_TO_SINK_UNKNOWN = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.DeviceProductInfo> CREATOR; + } + public final class DisplayManager { method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int); method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler); @@ -20279,6 +20367,10 @@ package android.media { field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_BIT_WIDTH; field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_CHANNEL_MASK; field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_MIME; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_ID; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_PRESENTATION_LANGUAGE; + field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PROGRAM_ID; field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_SAMPLE_RATE; } @@ -20333,6 +20425,15 @@ package android.media { method public boolean hasAudioDescription(); method public boolean hasDialogueEnhancement(); method public boolean hasSpokenSubtitles(); + field public static final int CONTENT_COMMENTARY = 5; // 0x5 + field public static final int CONTENT_DIALOG = 4; // 0x4 + field public static final int CONTENT_EMERGENCY = 6; // 0x6 + field public static final int CONTENT_HEARING_IMPAIRED = 3; // 0x3 + field public static final int CONTENT_MAIN = 0; // 0x0 + field public static final int CONTENT_MUSIC_AND_EFFECTS = 1; // 0x1 + field public static final int CONTENT_UNKNOWN = -1; // 0xffffffff + field public static final int CONTENT_VISUALLY_IMPAIRED = 2; // 0x2 + field public static final int CONTENT_VOICEOVER = 7; // 0x7 field public static final int MASTERED_FOR_3D = 3; // 0x3 field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4 field public static final int MASTERED_FOR_STEREO = 1; // 0x1 @@ -25866,6 +25967,7 @@ package android.net { method @NonNull public static java.util.Set<java.lang.String> getSupportedAlgorithms(); method public int getTruncationLengthBits(); method public void writeToParcel(android.os.Parcel, int); + field public static final String AUTH_AES_CMAC = "cmac(aes)"; field public static final String AUTH_AES_XCBC = "xcbc(aes)"; field public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))"; field public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)"; @@ -26007,6 +26109,16 @@ package android.net { field public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; // 0x6 } + public final class Proxy { + ctor public Proxy(); + method @Deprecated public static String getDefaultHost(); + method @Deprecated public static int getDefaultPort(); + method @Deprecated public static String getHost(android.content.Context); + method @Deprecated public static int getPort(android.content.Context); + field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; + field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + } + @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { ctor @Deprecated public SSLCertificateSocketFactory(int); method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException; @@ -26678,7 +26790,22 @@ package android.net.vcn { public class VcnManager { method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException; + method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback); method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException; + method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback); + field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1 + field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0 + field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2 + field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2 + field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1 + field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0 + field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3 + } + + public abstract static class VcnManager.VcnStatusCallback { + ctor public VcnManager.VcnStatusCallback(); + method public abstract void onGatewayConnectionError(@NonNull int[], int, @Nullable Throwable); + method public abstract void onVcnStatusChanged(int); } } @@ -30228,6 +30355,7 @@ package android.os { field public static final String HARDWARE; field public static final String HOST; field public static final String ID; + field public static final boolean IS_DEBUGGABLE; field public static final String MANUFACTURER; field public static final String MODEL; field @NonNull public static final String ODM_SKU; @@ -30386,19 +30514,10 @@ package android.os { public abstract class CombinedVibrationEffect implements android.os.Parcelable { method @NonNull public static android.os.CombinedVibrationEffect createSynced(@NonNull android.os.VibrationEffect); method public int describeContents(); - method @NonNull public static android.os.CombinedVibrationEffect.SequentialCombination startSequential(); method @NonNull public static android.os.CombinedVibrationEffect.SyncedCombination startSynced(); field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect> CREATOR; } - public static final class CombinedVibrationEffect.SequentialCombination { - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect); - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect, int); - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect); - method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect, int); - method @NonNull public android.os.CombinedVibrationEffect combine(); - } - public static final class CombinedVibrationEffect.SyncedCombination { method @NonNull public android.os.CombinedVibrationEffect.SyncedCombination addVibrator(int, @NonNull android.os.VibrationEffect); method @NonNull public android.os.CombinedVibrationEffect combine(); @@ -31823,6 +31942,7 @@ package android.os.storage { method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID) throws java.io.IOException; method @WorkerThread public long getCacheQuotaBytes(@NonNull java.util.UUID) throws java.io.IOException; method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException; + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public android.app.PendingIntent getManageSpaceActivityIntent(@NonNull String, int); method public String getMountedObbPath(String); method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume(); method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes(); @@ -38948,6 +39068,10 @@ package android.telecom { method public void unhold(); method public void unregisterCallback(android.telecom.Call.Callback); field @Deprecated public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; + field public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE = "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE"; + field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE"; + field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE"; + field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID"; field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS"; field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED"; field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS"; @@ -41312,7 +41436,6 @@ package android.telephony { method @NonNull public java.util.List<java.lang.Integer> getAvailableServices(); method @Nullable public android.telephony.CellIdentity getCellIdentity(); method public int getDomain(); - method public int getNrState(); method @Nullable public String getRegisteredPlmn(); method public int getTransportType(); method public boolean isRegistered(); @@ -41431,29 +41554,29 @@ package android.telephony { field public static final char WILD = 78; // 0x004e 'N' } - public class PhoneStateListener { - ctor public PhoneStateListener(); + @Deprecated public class PhoneStateListener { + ctor @Deprecated public PhoneStateListener(); ctor @Deprecated public PhoneStateListener(@NonNull java.util.concurrent.Executor); - method public void onActiveDataSubscriptionIdChanged(int); - method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo); - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int); - method public void onCallForwardingIndicatorChanged(boolean); - method public void onCallStateChanged(int, String); - method public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>); - method public void onCellLocationChanged(android.telephony.CellLocation); - method public void onDataActivity(int); - method public void onDataConnectionStateChanged(int); - method public void onDataConnectionStateChanged(int, int); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo); - method public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>); - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo); - method public void onMessageWaitingIndicatorChanged(boolean); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState); - method public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int); - method public void onServiceStateChanged(android.telephony.ServiceState); + method @Deprecated public void onActiveDataSubscriptionIdChanged(int); + method @Deprecated public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int); + method @Deprecated public void onCallForwardingIndicatorChanged(boolean); + method @Deprecated public void onCallStateChanged(int, String); + method @Deprecated public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>); + method @Deprecated public void onCellLocationChanged(android.telephony.CellLocation); + method @Deprecated public void onDataActivity(int); + method @Deprecated public void onDataConnectionStateChanged(int); + method @Deprecated public void onDataConnectionStateChanged(int, int); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo); + method @Deprecated public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo); + method @Deprecated public void onMessageWaitingIndicatorChanged(boolean); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState); + method @Deprecated public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int); + method @Deprecated public void onServiceStateChanged(android.telephony.ServiceState); method @Deprecated public void onSignalStrengthChanged(int); - method public void onSignalStrengthsChanged(android.telephony.SignalStrength); - method public void onUserMobileDataStateChanged(boolean); + method @Deprecated public void onSignalStrengthsChanged(android.telephony.SignalStrength); + method @Deprecated public void onUserMobileDataStateChanged(boolean); field @Deprecated public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000 field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000 field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 @@ -41467,7 +41590,7 @@ package android.telephony { field @Deprecated public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000 field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000 field @Deprecated public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4 - field public static final int LISTEN_NONE = 0; // 0x0 + field @Deprecated public static final int LISTEN_NONE = 0; // 0x0 field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000 field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000 field @Deprecated public static final int LISTEN_SERVICE_STATE = 1; // 0x1 @@ -41476,90 +41599,6 @@ package android.telephony { field @Deprecated public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000 } - public static interface PhoneStateListener.ActiveDataSubscriptionIdChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onActiveDataSubscriptionIdChanged(int); - } - - public static interface PhoneStateListener.AlwaysReportedSignalStrengthChangedListener { - method @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength); - } - - public static interface PhoneStateListener.BarringInfoChangedListener { - method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo); - } - - public static interface PhoneStateListener.CallDisconnectCauseChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int); - } - - public static interface PhoneStateListener.CallForwardingIndicatorChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onCallForwardingIndicatorChanged(boolean); - } - - public static interface PhoneStateListener.CallStateChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public void onCallStateChanged(int, @Nullable String); - } - - public static interface PhoneStateListener.CarrierNetworkChangeListener { - method public void onCarrierNetworkChange(boolean); - } - - public static interface PhoneStateListener.CellInfoChangedListener { - method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellInfoChanged(@NonNull java.util.List<android.telephony.CellInfo>); - } - - public static interface PhoneStateListener.CellLocationChangedListener { - method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellLocationChanged(@NonNull android.telephony.CellLocation); - } - - public static interface PhoneStateListener.DataActivationStateChangedListener { - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivationStateChanged(int); - } - - public static interface PhoneStateListener.DataActivityListener { - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivity(int); - } - - public static interface PhoneStateListener.DataConnectionStateChangedListener { - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataConnectionStateChanged(int, int); - } - - public static interface PhoneStateListener.DisplayInfoChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo); - } - - public static interface PhoneStateListener.EmergencyNumberListChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>); - } - - public static interface PhoneStateListener.ImsCallDisconnectCauseChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo); - } - - public static interface PhoneStateListener.MessageWaitingIndicatorChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onMessageWaitingIndicatorChanged(boolean); - } - - public static interface PhoneStateListener.PreciseDataConnectionStateChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState); - } - - public static interface PhoneStateListener.RegistrationFailedListener { - method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int); - } - - public static interface PhoneStateListener.ServiceStateChangedListener { - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onServiceStateChanged(@NonNull android.telephony.ServiceState); - } - - public static interface PhoneStateListener.SignalStrengthsChangedListener { - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength); - } - - public static interface PhoneStateListener.UserMobileDataStateChangedListener { - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onUserMobileDataStateChanged(boolean); - } - public final class PhysicalChannelConfig implements android.os.Parcelable { method public int describeContents(); method @IntRange(from=1, to=261) public int getBand(); @@ -42034,6 +42073,94 @@ package android.telephony { method public android.telephony.SubscriptionPlan.Builder setTitle(@Nullable CharSequence); } + public class TelephonyCallback { + ctor public TelephonyCallback(); + } + + public static interface TelephonyCallback.ActiveDataSubscriptionIdListener { + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onActiveDataSubscriptionIdChanged(int); + } + + public static interface TelephonyCallback.AlwaysReportedSignalStrengthListener { + method @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength); + } + + public static interface TelephonyCallback.BarringInfoListener { + method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo); + } + + public static interface TelephonyCallback.CallDisconnectCauseListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int); + } + + public static interface TelephonyCallback.CallForwardingIndicatorListener { + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onCallForwardingIndicatorChanged(boolean); + } + + public static interface TelephonyCallback.CallStateListener { + method @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public void onCallStateChanged(int, @Nullable String); + } + + public static interface TelephonyCallback.CarrierNetworkListener { + method public void onCarrierNetworkChange(boolean); + } + + public static interface TelephonyCallback.CellInfoListener { + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellInfoChanged(@NonNull java.util.List<android.telephony.CellInfo>); + } + + public static interface TelephonyCallback.CellLocationListener { + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellLocationChanged(@NonNull android.telephony.CellLocation); + } + + public static interface TelephonyCallback.DataActivationStateListener { + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivationStateChanged(int); + } + + public static interface TelephonyCallback.DataActivityListener { + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivity(int); + } + + public static interface TelephonyCallback.DataConnectionStateListener { + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataConnectionStateChanged(int, int); + } + + public static interface TelephonyCallback.DisplayInfoListener { + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo); + } + + public static interface TelephonyCallback.EmergencyNumberListListener { + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>); + } + + public static interface TelephonyCallback.ImsCallDisconnectCauseListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo); + } + + public static interface TelephonyCallback.MessageWaitingIndicatorListener { + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onMessageWaitingIndicatorChanged(boolean); + } + + public static interface TelephonyCallback.PreciseDataConnectionStateListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState); + } + + public static interface TelephonyCallback.RegistrationFailedListener { + method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int); + } + + public static interface TelephonyCallback.ServiceStateListener { + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onServiceStateChanged(@NonNull android.telephony.ServiceState); + } + + public static interface TelephonyCallback.SignalStrengthsListener { + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength); + } + + public static interface TelephonyCallback.UserMobileDataStateListener { + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onUserMobileDataStateChanged(boolean); + } + public final class TelephonyDisplayInfo implements android.os.Parcelable { method public int describeContents(); method public int getNetworkType(); @@ -42146,7 +42273,7 @@ package android.telephony { method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method @Deprecated public void listen(android.telephony.PhoneStateListener, int); - method public void registerPhoneStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.PhoneStateListener); + method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); method public void sendDialerSpecialCode(String); @@ -42170,7 +42297,7 @@ package android.telephony { method @Deprecated public void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri); method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void switchMultiSimConfig(int); - method public void unregisterPhoneStateListener(@NonNull android.telephony.PhoneStateListener); + method public void unregisterTelephonyCallback(@NonNull android.telephony.TelephonyCallback); method public void updateAvailableNetworks(@NonNull java.util.List<android.telephony.AvailableNetworkInfo>, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); method public void uploadCallComposerPicture(@NonNull java.nio.file.Path, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.ParcelUuid,android.telephony.TelephonyManager.CallComposerException>); method public void uploadCallComposerPicture(@NonNull java.io.InputStream, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.ParcelUuid,android.telephony.TelephonyManager.CallComposerException>); @@ -42199,7 +42326,6 @@ package android.telephony { field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80 field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0 field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1 - field public static final int CALL_COMPOSER_STATUS_ON_NO_PICTURES = 2; // 0x2 field public static final int CALL_STATE_IDLE = 0; // 0x0 field public static final int CALL_STATE_OFFHOOK = 2; // 0x2 field public static final int CALL_STATE_RINGING = 1; // 0x1 @@ -45834,11 +45960,14 @@ package android.util { method public static android.util.Size parseSize(String) throws java.lang.NumberFormatException; } - public final class SizeF { + public final class SizeF implements android.os.Parcelable { ctor public SizeF(float, float); + method public int describeContents(); method public float getHeight(); method public float getWidth(); method public static android.util.SizeF parseSizeF(String) throws java.lang.NumberFormatException; + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.util.SizeF> CREATOR; } public class SparseArray<E> implements java.lang.Cloneable { @@ -45848,7 +45977,7 @@ package android.util { method public void clear(); method public android.util.SparseArray<E> clone(); method public boolean contains(int); - method public boolean contentEquals(@Nullable android.util.SparseArray<E>); + method public boolean contentEquals(@Nullable android.util.SparseArray<?>); method public int contentHashCode(); method public void delete(int); method public E get(int); @@ -46244,6 +46373,7 @@ package android.view { method public long getAppVsyncOffsetNanos(); method public void getCurrentSizeRange(android.graphics.Point, android.graphics.Point); method @Nullable public android.view.DisplayCutout getCutout(); + method @Nullable public android.hardware.display.DeviceProductInfo getDeviceProductInfo(); method public int getDisplayId(); method public int getFlags(); method public android.view.Display.HdrCapabilities getHdrCapabilities(); @@ -49720,9 +49850,12 @@ package android.view { } public interface WindowManager extends android.view.ViewManager { + method public default void addCrossWindowBlurEnabledListener(@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(); + method public default boolean isCrossWindowBlurEnabled(); + method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void removeViewImmediate(android.view.View); } @@ -49747,12 +49880,14 @@ package android.view { method public final int copyFrom(android.view.WindowManager.LayoutParams); method public String debug(String); method public int describeContents(); + method public int getBlurBehindRadius(); method public int getColorMode(); method public int getFitInsetsSides(); method public int getFitInsetsTypes(); method public final CharSequence getTitle(); method public boolean isFitInsetsIgnoringVisibility(); method public static boolean mayUseInputMethod(int); + method public void setBlurBehindRadius(@IntRange(from=0) int); method public void setColorMode(int); method public void setFitInsetsIgnoringVisibility(boolean); method public void setFitInsetsSides(int); @@ -49863,7 +49998,6 @@ package android.view { field @Deprecated public static final int TYPE_TOAST = 2005; // 0x7d5 field public static final int TYPE_WALLPAPER = 2013; // 0x7dd field public float alpha; - field public int blurBehindRadius; field public float buttonBrightness; field public float dimAmount; field public int flags; @@ -50028,7 +50162,7 @@ package android.view.accessibility { method public boolean canOpenPopup(); method public int describeContents(); method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String); - method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String); + method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int); method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList(); @@ -50930,6 +51064,7 @@ package android.view.displayhash { method public void onDisplayHashError(int); method public void onDisplayHashResult(@NonNull android.view.displayhash.DisplayHash); field public static final int DISPLAY_HASH_ERROR_INVALID_BOUNDS = -2; // 0xfffffffe + field public static final int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; // 0xfffffffb field public static final int DISPLAY_HASH_ERROR_MISSING_WINDOW = -3; // 0xfffffffd field public static final int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4; // 0xfffffffc field public static final int DISPLAY_HASH_ERROR_UNKNOWN = -1; // 0xffffffff @@ -54724,7 +54859,7 @@ package android.widget { public class RemoteViews implements android.view.LayoutInflater.Filter android.os.Parcelable { ctor public RemoteViews(String, int); ctor public RemoteViews(android.widget.RemoteViews, android.widget.RemoteViews); - ctor public RemoteViews(@NonNull java.util.Map<android.graphics.PointF,android.widget.RemoteViews>); + ctor public RemoteViews(@NonNull java.util.Map<android.util.SizeF,android.widget.RemoteViews>); ctor public RemoteViews(android.widget.RemoteViews); ctor public RemoteViews(android.os.Parcel); method public void addView(@IdRes int, android.widget.RemoteViews); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 7ea7d61ac3c5..ad2942a2079e 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -35,7 +35,7 @@ package android.app { } public final class PendingIntent implements android.os.Parcelable { - method @Nullable @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int); + method @NonNull @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int); } public class StatusBarManager { @@ -44,6 +44,14 @@ package android.app { } +package android.app.usage { + + public class NetworkStatsManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>); + } + +} + package android.content { public abstract class Context { @@ -167,10 +175,26 @@ package android.net { method public int getResourceId(); } + 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(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR; + field public final int legacyType; + field @NonNull public final android.net.LinkProperties linkProperties; + field @NonNull public final android.net.Network network; + field @NonNull public final android.net.NetworkCapabilities networkCapabilities; + field @Nullable public final String subscriberId; + } + public class NetworkWatchlistManager { method @Nullable public byte[] getWatchlistConfigHash(); } + public final class Proxy { + method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); + } + public final class UnderlyingNetworkInfo implements android.os.Parcelable { ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>); method public int describeContents(); @@ -217,8 +241,8 @@ package android.os { package android.os.storage { public class StorageManager { - method public void notifyAppIoBlocked(@NonNull String, int, int, int); - method public void notifyAppIoResumed(@NonNull String, int, int, int); + method public void notifyAppIoBlocked(@NonNull java.util.UUID, int, int, int); + method public void notifyAppIoResumed(@NonNull java.util.UUID, int, int, int); field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 0; // 0x0 } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 3da6add3a04e..3a238e29dd23 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -32,6 +32,7 @@ package android { field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; + field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE"; field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE"; @@ -153,6 +154,7 @@ package android { field public static final String MANAGE_USB = "android.permission.MANAGE_USB"; field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS"; field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE"; + field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE"; field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"; field public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING"; field public static final String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS"; @@ -356,6 +358,7 @@ package android { field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemShell = 17039402; // 0x104002a field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e + field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f } public static final class R.style { @@ -422,6 +425,9 @@ package android.app { method @Nullable public static String opToPermission(@NonNull String); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int); + field public static final int HISTORY_FLAGS_ALL = 3; // 0x3 + field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1 + field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2 field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility"; field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; @@ -535,9 +541,14 @@ package android.app { method public long getAccessDuration(int, int, int); method public long getBackgroundAccessCount(int); method public long getBackgroundAccessDuration(int); + method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getBackgroundDiscreteAccesses(int); method public long getBackgroundRejectCount(int); + method @NonNull public android.app.AppOpsManager.AttributedOpEntry getDiscreteAccessAt(@IntRange(from=0) int); + method @IntRange(from=0) public int getDiscreteAccessCount(); + method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getDiscreteAccesses(int, int, int); method public long getForegroundAccessCount(int); method public long getForegroundAccessDuration(int); + method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getForegroundDiscreteAccesses(int); method public long getForegroundRejectCount(int); method @NonNull public String getOpName(); method public long getRejectCount(int, int, int); @@ -564,6 +575,7 @@ package android.app { method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build(); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setAttributionTag(@Nullable String); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setHistoryFlags(int); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setUid(int); @@ -857,13 +869,6 @@ package android.app { method public void onVrStateChanged(boolean); } - public final class WallpaperColors implements android.os.Parcelable { - ctor public WallpaperColors(@NonNull android.graphics.Color, @Nullable android.graphics.Color, @Nullable android.graphics.Color, int); - method public int getColorHints(); - field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 - field public static final int HINT_SUPPORTS_DARK_THEME = 2; // 0x2 - } - public final class WallpaperInfo implements android.os.Parcelable { method public boolean supportsAmbientMode(); } @@ -961,6 +966,9 @@ package android.app.admin { package android.app.assist { + public class ActivityId { + } + public static class AssistStructure.ViewNode { ctor public AssistStructure.ViewNode(); } @@ -1891,11 +1899,18 @@ package android.bluetooth { field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + field public static final String DEVICE_TYPE_DEFAULT = "Default"; + field public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset"; + field public static final String DEVICE_TYPE_WATCH = "Watch"; field public static final int METADATA_COMPANION_APP = 4; // 0x4 + field public static final int METADATA_DEVICE_TYPE = 17; // 0x11 field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 field public static final int METADATA_IS_UNTETHERED_HEADSET = 6; // 0x6 + field public static final int METADATA_MAIN_BATTERY = 18; // 0x12 + field public static final int METADATA_MAIN_CHARGING = 19; // 0x13 field public static final int METADATA_MAIN_ICON = 5; // 0x5 + field public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; // 0x14 field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0 field public static final int METADATA_MAX_LENGTH = 2048; // 0x800 field public static final int METADATA_MODEL_NAME = 1; // 0x1 @@ -1903,12 +1918,15 @@ package android.bluetooth { field public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; // 0xc field public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; // 0xf field public static final int METADATA_UNTETHERED_CASE_ICON = 9; // 0x9 + field public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; // 0x17 field public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; // 0xa field public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; // 0xd field public static final int METADATA_UNTETHERED_LEFT_ICON = 7; // 0x7 + field public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; // 0x15 field public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; // 0xb field public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; // 0xe field public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; // 0x8 + field public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; // 0x16 } public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { @@ -2556,9 +2574,7 @@ package android.content.pm { field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; field public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; - field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur"; - field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; - field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version"; + field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; @@ -2687,7 +2703,7 @@ package android.content.pm { } public class ShortcutManager { - method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS) public java.util.List<android.content.pm.ShortcutManager.ShareShortcutInfo> getShareTargets(@NonNull android.content.IntentFilter); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS) @WorkerThread public java.util.List<android.content.pm.ShortcutManager.ShareShortcutInfo> getShareTargets(@NonNull android.content.IntentFilter); method public boolean hasShareTargets(@NonNull String); } @@ -2774,13 +2790,12 @@ package android.content.pm.verify.domain { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR; } - public interface DomainVerificationManager { + public final class DomainVerificationManager { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String); - method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames(); 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; @@ -2797,18 +2812,8 @@ package android.content.pm.verify.domain { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationRequest> CREATOR; } - public final class DomainVerificationUserSelection implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); + public final class DomainVerificationUserState implements android.os.Parcelable { method @NonNull public java.util.UUID getIdentifier(); - method @NonNull public String getPackageName(); - method @NonNull public android.os.UserHandle getUser(); - method @NonNull public boolean isLinkHandlingAllowed(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR; - field public static final int DOMAIN_STATE_NONE = 0; // 0x0 - field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 - field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 } } @@ -3445,6 +3450,10 @@ package android.hardware.lights { field @Deprecated public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8 } + public static final class LightsRequest.Builder { + method @Deprecated @NonNull public android.hardware.lights.LightsRequest.Builder setLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState); + } + } package android.hardware.location { @@ -5087,9 +5096,10 @@ package android.media { } public static class AudioTrack.TunerConfiguration { - ctor @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public AudioTrack.TunerConfiguration(@IntRange(from=1) int, @IntRange(from=1) int); + ctor @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public AudioTrack.TunerConfiguration(@IntRange(from=0) int, @IntRange(from=1) int); method @IntRange(from=1) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getContentId(); method @IntRange(from=1) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getSyncId(); + field public static final int CONTENT_ID_NONE = 0; // 0x0 } public class HwAudioSource { @@ -7155,9 +7165,6 @@ package android.net { method public abstract void onRequestScores(android.net.NetworkKey[]); } - public class NetworkReleasedException extends java.lang.Exception { - } - public class NetworkScoreManager { method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException; method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException; @@ -7241,47 +7248,6 @@ package android.net { method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String); } - public abstract class QosCallback { - ctor public QosCallback(); - method public void onError(@NonNull android.net.QosCallbackException); - method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes); - method public void onQosSessionLost(@NonNull android.net.QosSession); - } - - public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException { - } - - public final class QosCallbackException extends java.lang.Exception { - } - - public abstract class QosFilter { - method @NonNull public abstract android.net.Network getNetwork(); - method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int); - } - - public final class QosSession implements android.os.Parcelable { - ctor public QosSession(int, int); - method public int describeContents(); - method public int getSessionId(); - method public int getSessionType(); - method public long getUniqueId(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR; - field public static final int TYPE_EPS_BEARER = 1; // 0x1 - } - - public interface QosSessionAttributes { - } - - public final class QosSocketInfo implements android.os.Parcelable { - ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException; - method public int describeContents(); - method @NonNull public java.net.InetSocketAddress getLocalSocketAddress(); - method @NonNull public android.net.Network getNetwork(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR; - } - public class RssiCurve implements android.os.Parcelable { ctor public RssiCurve(int, int, byte[]); ctor public RssiCurve(int, int, byte[], int); @@ -7313,12 +7279,6 @@ package android.net { field public final android.net.RssiCurve rssiCurve; } - public class SocketLocalAddressChangedException extends java.lang.Exception { - } - - public class SocketNotBoundException extends java.lang.Exception { - } - public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); @@ -7548,6 +7508,19 @@ package android.net.sip { } +package android.net.util { + + public final class SocketUtils { + method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; + method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; + method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); + method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); + } + +} + package android.net.vcn { public class VcnManager { @@ -8246,10 +8219,11 @@ package android.os { 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 = 102; // 0x66 + 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 @@ -8865,6 +8839,8 @@ package android.provider { field public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native"; field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot"; field public static final String NAMESPACE_SCHEDULER = "scheduler"; + field public static final String NAMESPACE_STATSD_JAVA = "statsd_java"; + field public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot"; field public static final String NAMESPACE_STATSD_NATIVE = "statsd_native"; field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot"; field @Deprecated public static final String NAMESPACE_STORAGE = "storage"; @@ -10380,6 +10356,16 @@ package android.telecom { ctor @Deprecated public Call.Listener(); } + public abstract class CallDiagnosticService extends android.app.Service { + ctor public CallDiagnosticService(); + 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); + field public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService"; + } + public static class CallScreeningService.CallResponse.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public android.telecom.CallScreeningService.CallResponse.Builder setShouldScreenCallViaAudioProcessing(boolean); } @@ -10412,6 +10398,9 @@ package android.telecom { method public void setTelecomCallId(@NonNull String); field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000 field public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 262144; // 0x40000 + field public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE = "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE"; + field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE"; + field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE"; field public static final String EXTRA_DISABLE_ADD_CALL = "android.telecom.extra.DISABLE_ADD_CALL"; field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 1; // 0x1 field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 @@ -10437,6 +10426,34 @@ package android.telecom { 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 + } + public abstract class InCallService extends android.app.Service { method @Deprecated public android.telecom.Phone getPhone(); method @Deprecated public void onPhoneCreated(android.telecom.Phone); @@ -11060,51 +11077,16 @@ package android.telephony { method public static boolean isVoiceMailNumber(@NonNull android.content.Context, int, @Nullable String); } - public class PhoneStateListener { - method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); + @Deprecated public class PhoneStateListener { + method @Deprecated public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber); - method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int); + method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int); method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber); - method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int); - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState); - method public void onRadioPowerStateChanged(int); - method public void onSrvccStateChanged(int); - method public void onVoiceActivationStateChanged(int); - field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23; // 0x17 - field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED = 35; // 0x23 - field @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10; // 0xa - field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_BARRING_INFO_CHANGED = 32; // 0x20 - field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27; // 0x1b - field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26; // 0x1a - field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4 - field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6 - field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11 - field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb - field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5 - field public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; // 0x13 - field public static final int EVENT_DATA_ACTIVITY_CHANGED = 8; // 0x8 - field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14; // 0xe - field public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7; // 0x7 - field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_ENABLED_CHANGED = 34; // 0x22 - field public static final int EVENT_DISPLAY_INFO_CHANGED = 21; // 0x15 - 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_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 - field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30; // 0x1e - field public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22; // 0x16 - field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33; // 0x21 - field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12; // 0xc - field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; // 0xd - field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; // 0x18 - field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_REGISTRATION_FAILURE = 31; // 0x1f - field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1 - field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9 - field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2 - field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10 - field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14 - field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12 + method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState); + method @Deprecated public void onRadioPowerStateChanged(int); + method @Deprecated public void onSrvccStateChanged(int); + method @Deprecated public void onVoiceActivationStateChanged(int); field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000 field @Deprecated @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000 field @Deprecated @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000 @@ -11114,50 +11096,6 @@ package android.telephony { field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000 } - public static interface PhoneStateListener.AllowedNetworkTypesChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onAllowedNetworkTypesChanged(@NonNull java.util.Map<java.lang.Integer,java.lang.Long>); - } - - public static interface PhoneStateListener.CallAttributesChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); - } - - public static interface PhoneStateListener.DataEnabledChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onDataEnabledChanged(boolean, int); - } - - public static interface PhoneStateListener.OutgoingEmergencyCallListener { - method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int); - } - - public static interface PhoneStateListener.OutgoingEmergencySmsListener { - method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int); - } - - public static interface PhoneStateListener.PhoneCapabilityChangedListener { - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability); - } - - public static interface PhoneStateListener.PhysicalChannelConfigChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>); - } - - public static interface PhoneStateListener.PreciseCallStateChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState); - } - - public static interface PhoneStateListener.RadioPowerStateChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int); - } - - public static interface PhoneStateListener.SrvccStateChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int); - } - - public static interface PhoneStateListener.VoiceActivationStateChangedListener { - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onVoiceActivationStateChanged(int); - } - public final class PinResult implements android.os.Parcelable { method public int describeContents(); method public int getAttemptsRemaining(); @@ -11495,6 +11433,88 @@ package android.telephony { method @Deprecated public static android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime); } + public class TelephonyCallback { + field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23; // 0x17 + field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED = 35; // 0x23 + field @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10; // 0xa + field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_BARRING_INFO_CHANGED = 32; // 0x20 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27; // 0x1b + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26; // 0x1a + field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4 + field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6 + field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11 + field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb + field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5 + field public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; // 0x13 + field public static final int EVENT_DATA_ACTIVITY_CHANGED = 8; // 0x8 + field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14; // 0xe + field public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7; // 0x7 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_ENABLED_CHANGED = 34; // 0x22 + field public static final int EVENT_DISPLAY_INFO_CHANGED = 21; // 0x15 + 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_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 + field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30; // 0x1e + field public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22; // 0x16 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33; // 0x21 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12; // 0xc + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; // 0xd + field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; // 0x18 + field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_REGISTRATION_FAILURE = 31; // 0x1f + field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1 + field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9 + field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2 + field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10 + field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14 + field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12 + } + + 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>); + } + + public static interface TelephonyCallback.CallAttributesListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); + } + + public static interface TelephonyCallback.DataEnabledListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onDataEnabledChanged(boolean, int); + } + + public static interface TelephonyCallback.OutgoingEmergencyCallListener { + method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int); + } + + public static interface TelephonyCallback.OutgoingEmergencySmsListener { + method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int); + } + + public static interface TelephonyCallback.PhoneCapabilityListener { + 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); + } + + public static interface TelephonyCallback.RadioPowerStateListener { + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int); + } + + public static interface TelephonyCallback.SrvccStateListener { + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int); + } + + public static interface TelephonyCallback.VoiceActivationStateListener { + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onVoiceActivationStateChanged(int); + } + public final class TelephonyHistogram implements android.os.Parcelable { ctor public TelephonyHistogram(int, int, int); ctor public TelephonyHistogram(android.telephony.TelephonyHistogram); @@ -13038,7 +13058,7 @@ package android.telephony.ims { method @NonNull public String getServiceId(); method @NonNull public String getServiceVersion(); method @NonNull public String getStatus(); - method @Nullable public String getTimestamp(); + method @Nullable public java.time.Instant getTime(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactPresenceTuple> CREATOR; field public static final String SERVICE_ID_CALL_COMPOSER = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callcomposer"; @@ -13065,7 +13085,7 @@ package android.telephony.ims { method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setContactUri(@NonNull android.net.Uri); method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setServiceCapabilities(@NonNull android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities); method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setServiceDescription(@NonNull String); - method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setTimestamp(@NonNull String); + method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setTime(@NonNull java.time.Instant); } public static final class RcsContactPresenceTuple.ServiceCapabilities implements android.os.Parcelable { @@ -13121,7 +13141,7 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException; method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; - method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException; field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1 @@ -13284,10 +13304,12 @@ package android.telephony.ims { public final class SipMessage implements android.os.Parcelable { ctor public SipMessage(@NonNull String, @NonNull String, @NonNull byte[]); method public int describeContents(); + method @Nullable public String getCallIdParameter(); method @NonNull public byte[] getContent(); method @NonNull public byte[] getEncodedMessage(); method @NonNull public String getHeaderSection(); method @NonNull public String getStartLine(); + method @Nullable public String getViaBranchParameter(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SipMessage> CREATOR; } @@ -13597,7 +13619,7 @@ package android.telephony.ims.stub { ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor); method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback); method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback); - method public void subscribeForCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback); + method public void subscribeForCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback); field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3 field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1 field public static final int COMMAND_CODE_INSUFFICIENT_MEMORY = 5; // 0x5 @@ -14003,6 +14025,7 @@ package android.view.contentcapture { public final class ContentCaptureContext implements android.os.Parcelable { method @Nullable public android.content.ComponentName getActivityComponent(); + method @Nullable public android.app.assist.ActivityId getActivityId(); method public int getDisplayId(); method public int getFlags(); method @Nullable public android.view.contentcapture.ContentCaptureSessionId getParentSessionId(); @@ -14065,9 +14088,13 @@ package android.view.translation { public final class UiTranslationManager { method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId); } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 6ee57d6b0392..b41f9702c7c1 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -224,6 +224,9 @@ package android.app { field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2 + field public static final int HISTORY_FLAGS_ALL = 3; // 0x3 + field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1 + field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2 field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time"; field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; @@ -238,6 +241,7 @@ package android.app { public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable { ctor public AppOpsManager.HistoricalOps(long, long); + method public void addDiscreteAccess(int, int, @NonNull String, @Nullable String, int, int, long, long); method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long); method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long); method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long); @@ -294,7 +298,7 @@ package android.app { } public final class PendingIntent implements android.os.Parcelable { - method @Nullable @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int); + method @NonNull @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int); field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000 } @@ -399,6 +403,8 @@ package android.app.admin { method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int); method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs(); method public void forceUpdateUserSetupComplete(); + method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages(); + method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String); method public long getLastBugReportRequestTime(); method public long getLastNetworkLogRetrievalTime(); method public long getLastSecurityLogRetrievalTime(); @@ -544,6 +550,15 @@ package android.app.admin { } +package android.app.assist { + + public class ActivityId { + method public int getTaskId(); + method @Nullable public android.os.IBinder getToken(); + } + +} + package android.app.blob { public class BlobStoreManager { @@ -1474,6 +1489,7 @@ package android.os { public abstract class CombinedVibrationEffect implements android.os.Parcelable { method public abstract long getDuration(); + method @NonNull public static android.os.CombinedVibrationEffect.SequentialCombination startSequential(); } public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect { @@ -1491,6 +1507,14 @@ package android.os { field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR; } + public static final class CombinedVibrationEffect.SequentialCombination { + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect); + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect, int); + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect); + method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect, int); + method @NonNull public android.os.CombinedVibrationEffect combine(); + } + public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect { method public long getDuration(); method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a73fe71cfe1a..f5f0b422a69d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -803,6 +803,7 @@ public class Activity extends ContextThemeWrapper @UnsupportedAppUsage private IBinder mToken; private IBinder mAssistToken; + private IBinder mShareableActivityToken; @UnsupportedAppUsage private int mIdent; @UnsupportedAppUsage @@ -1210,7 +1211,7 @@ public class Activity extends ContextThemeWrapper if (window != null) { cm.updateWindowAttributes(window.getAttributes()); } - cm.onActivityCreated(mToken, getComponentName()); + cm.onActivityCreated(mToken, mShareableActivityToken, getComponentName()); break; case CONTENT_CAPTURE_RESUME: cm.onActivityResumed(); @@ -7118,6 +7119,9 @@ public class Activity extends ContextThemeWrapper case "--contentcapture": dumpContentCaptureManager(prefix, writer); return; + case "--translation": + dumpUiTranslation(prefix, writer); + return; } } writer.print(prefix); writer.print("Local Activity "); @@ -7158,6 +7162,7 @@ public class Activity extends ContextThemeWrapper dumpAutofillManager(prefix, writer); dumpContentCaptureManager(prefix, writer); + dumpUiTranslation(prefix, writer); ResourcesManager.getInstance().dump(prefix, writer); } @@ -7182,6 +7187,14 @@ public class Activity extends ContextThemeWrapper } } + void dumpUiTranslation(String prefix, PrintWriter writer) { + if (mUiTranslationController != null) { + mUiTranslationController.dump(prefix, writer); + } else { + writer.print(prefix); writer.println("No UiTranslationController"); + } + } + /** * Bit indicating that this activity is "immersive" and should not be * interrupted by notifications if possible. @@ -7838,7 +7851,8 @@ public class Activity extends ContextThemeWrapper CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, - Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) { + Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken, + IBinder shareableActivityToken) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); @@ -7860,6 +7874,7 @@ public class Activity extends ContextThemeWrapper mInstrumentation = instr; mToken = token; mAssistToken = assistToken; + mShareableActivityToken = shareableActivityToken; mIdent = ident; mApplication = application; mIntent = intent; @@ -7918,6 +7933,11 @@ public class Activity extends ContextThemeWrapper } /** @hide */ + public final IBinder getShareableActivityToken() { + return mParent != null ? mParent.getShareableActivityToken() : mShareableActivityToken; + } + + /** @hide */ @VisibleForTesting public final ActivityThread getActivityThread() { return mMainThread; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 47d2e7cee65a..e7751b861037 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -176,6 +176,8 @@ import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; +import android.view.contentcapture.IContentCaptureManager; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.view.translation.TranslationSpec; import android.webkit.WebView; import android.window.SplashScreen; @@ -512,11 +514,16 @@ public final class ActivityThread extends ClientTransactionHandler { boolean mHasImeComponent = false; + private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null; + /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */ public static final class ActivityClientRecord { @UnsupportedAppUsage public IBinder token; public IBinder assistToken; + // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be + // used without security checks + public IBinder shareableActivityToken; int ident; @UnsupportedAppUsage Intent intent; @@ -604,9 +611,11 @@ public final class ActivityThread extends ClientTransactionHandler { PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client, - IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments) { + IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments, + IBinder shareableActivityToken) { this.token = token; this.assistToken = assistToken; + this.shareableActivityToken = shareableActivityToken; this.ident = ident; this.intent = intent; this.referrer = referrer; @@ -1934,6 +1943,7 @@ public final class ActivityThread extends ClientTransactionHandler { public static final int PURGE_RESOURCES = 161; public static final int ATTACH_STARTUP_AGENTS = 162; public static final int UPDATE_UI_TRANSLATION_STATE = 163; + public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164; public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; @@ -1983,6 +1993,8 @@ public final class ActivityThread extends ClientTransactionHandler { case PURGE_RESOURCES: return "PURGE_RESOURCES"; case ATTACH_STARTUP_AGENTS: return "ATTACH_STARTUP_AGENTS"; case UPDATE_UI_TRANSLATION_STATE: return "UPDATE_UI_TRANSLATION_STATE"; + case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK: + return "SET_CONTENT_CAPTURE_OPTIONS_CALLBACK"; case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART"; case FINISH_INSTRUMENTATION_WITHOUT_RESTART: return "FINISH_INSTRUMENTATION_WITHOUT_RESTART"; @@ -2175,6 +2187,9 @@ public final class ActivityThread extends ClientTransactionHandler { (TranslationSpec) args.arg3, (TranslationSpec) args.arg4, (List<AutofillId>) args.arg5); break; + case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK: + handleSetContentCaptureOptionsCallback((String) msg.obj); + break; case INSTRUMENT_WITHOUT_RESTART: handleInstrumentWithoutRestart((AppBindData) msg.obj); break; @@ -3157,11 +3172,13 @@ public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public final Activity startActivityNow(Activity parent, String id, - Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, - Activity.NonConfigurationInstances lastNonConfigurationInstances, IBinder assistToken) { + Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, + Activity.NonConfigurationInstances lastNonConfigurationInstances, IBinder assistToken, + IBinder shareableActivityToken) { ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.assistToken = assistToken; + r.shareableActivityToken = shareableActivityToken; r.ident = 0; r.intent = intent; r.state = state; @@ -3504,7 +3521,7 @@ public final class ActivityThread extends ClientTransactionHandler { r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, - r.assistToken); + r.assistToken, r.shareableActivityToken); if (customIntent != null) { activity.mIntent = customIntent; @@ -6788,6 +6805,7 @@ public final class ActivityThread extends ClientTransactionHandler { // Propagate Content Capture options app.setContentCaptureOptions(data.contentCaptureOptions); + sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName); mInitialApplication = app; @@ -6849,6 +6867,36 @@ public final class ActivityThread extends ClientTransactionHandler { } } + private void handleSetContentCaptureOptionsCallback(String packageName) { + if (mContentCaptureOptionsCallback != null) { + return; + } + + IBinder b = ServiceManager.getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE); + if (b == null) { + return; + } + + IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(b); + mContentCaptureOptionsCallback = new IContentCaptureOptionsCallback.Stub() { + @Override + public void setContentCaptureOptions(ContentCaptureOptions options) + throws RemoteException { + if (mInitialApplication != null) { + mInitialApplication.setContentCaptureOptions(options); + } + } + }; + try { + service.registerContentCaptureOptionsCallback(packageName, + mContentCaptureOptionsCallback); + } catch (RemoteException e) { + Slog.w(TAG, "registerContentCaptureOptionsCallback() failed: " + + packageName, e); + mContentCaptureOptionsCallback = null; + } + } + private void handleInstrumentWithoutRestart(AppBindData data) { try { data.compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 160844aacc46..dd1bc7c61547 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,6 +16,8 @@ package android.app; +import static java.lang.Long.max; + import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -3385,6 +3387,13 @@ public class AppOpsManager { @DataClass.ParcelWith(LongSparseArrayParceling.class) private final @Nullable LongSparseArray<NoteOpEvent> mRejectEvents; + private AttributedOpEntry(@NonNull AttributedOpEntry other) { + mOp = other.mOp; + mRunning = other.mRunning; + mAccessEvents = other.mAccessEvents == null ? null : other.mAccessEvents.clone(); + mRejectEvents = other.mRejectEvents == null ? null : other.mRejectEvents.clone(); + } + /** * Returns all keys for which we have events. * @@ -3749,6 +3758,15 @@ public class AppOpsManager { return lastEvent.getProxy(); } + @NonNull + String getOpName() { + return AppOpsManager.opToPublicName(mOp); + } + + int getOp() { + return mOp; + } + private static class LongSparseArrayParceling implements Parcelling<LongSparseArray<NoteOpEvent>> { @Override @@ -4571,6 +4589,50 @@ public class AppOpsManager { } /** + * Flag for querying app op history: get only aggregate information and no + * discrete accesses. + * + * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer) + * + * @hide + */ + @TestApi + @SystemApi + public static final int HISTORY_FLAG_AGGREGATE = 1 << 0; + + /** + * Flag for querying app op history: get only discrete information and no + * aggregate accesses. + * + * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer) + * + * @hide + */ + @TestApi + @SystemApi + public static final int HISTORY_FLAG_DISCRETE = 1 << 1; + + /** + * Flag for querying app op history: get all types of historical accesses. + * + * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer) + * + * @hide + */ + @TestApi + @SystemApi + public static final int HISTORY_FLAGS_ALL = HISTORY_FLAG_AGGREGATE + | HISTORY_FLAG_DISCRETE; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "HISTORY_FLAG_" }, value = { + HISTORY_FLAG_AGGREGATE, + HISTORY_FLAG_DISCRETE + }) + public @interface OpHistoryFlags {} + + /** * Specifies what parameters to filter historical appop requests for * * @hide @@ -4625,6 +4687,7 @@ public class AppOpsManager { private final @Nullable String mPackageName; private final @Nullable String mAttributionTag; private final @Nullable List<String> mOpNames; + private final @OpHistoryFlags int mHistoryFlags; private final @HistoricalOpsRequestFilter int mFilter; private final long mBeginTimeMillis; private final long mEndTimeMillis; @@ -4632,12 +4695,13 @@ public class AppOpsManager { private HistoricalOpsRequest(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable List<String> opNames, - @HistoricalOpsRequestFilter int filter, long beginTimeMillis, - long endTimeMillis, @OpFlags int flags) { + @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, + long beginTimeMillis, long endTimeMillis, @OpFlags int flags) { mUid = uid; mPackageName = packageName; mAttributionTag = attributionTag; mOpNames = opNames; + mHistoryFlags = historyFlags; mFilter = filter; mBeginTimeMillis = beginTimeMillis; mEndTimeMillis = endTimeMillis; @@ -4655,6 +4719,7 @@ public class AppOpsManager { private @Nullable String mPackageName; private @Nullable String mAttributionTag; private @Nullable List<String> mOpNames; + private @OpHistoryFlags int mHistoryFlags; private @HistoricalOpsRequestFilter int mFilter; private final long mBeginTimeMillis; private final long mEndTimeMillis; @@ -4676,6 +4741,7 @@ public class AppOpsManager { "beginTimeMillis must be non negative and lesser than endTimeMillis"); mBeginTimeMillis = beginTimeMillis; mEndTimeMillis = endTimeMillis; + mHistoryFlags = HISTORY_FLAG_AGGREGATE; } /** @@ -4772,11 +4838,25 @@ public class AppOpsManager { } /** + * Specifies what type of historical information to query. + * + * @param flags Flags for the historical types to fetch which are any + * combination of {@link #HISTORY_FLAG_AGGREGATE}, {@link #HISTORY_FLAG_DISCRETE}, + * {@link #HISTORY_FLAGS_ALL}. The default is {@link #HISTORY_FLAG_AGGREGATE}. + * @return This builder. + */ + public @NonNull Builder setHistoryFlags(@OpHistoryFlags int flags) { + Preconditions.checkFlagsArgument(flags, HISTORY_FLAGS_ALL); + mHistoryFlags = flags; + return this; + } + + /** * @return a new {@link HistoricalOpsRequest}. */ public @NonNull HistoricalOpsRequest build() { return new HistoricalOpsRequest(mUid, mPackageName, mAttributionTag, mOpNames, - mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags); + mHistoryFlags, mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags); } } } @@ -4943,7 +5023,8 @@ public class AppOpsManager { * @hide */ public void filter(int uid, @Nullable String packageName, @Nullable String attributionTag, - @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, + @Nullable String[] opNames, @OpHistoryFlags int historyFilter, + @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis) { final long durationMillis = getDurationMillis(); mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis); @@ -4956,7 +5037,8 @@ public class AppOpsManager { if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) { mHistoricalUidOps.removeAt(i); } else { - uidOp.filter(packageName, attributionTag, opNames, filter, scaleFactor); + uidOp.filter(packageName, attributionTag, opNames, filter, historyFilter, + scaleFactor, mBeginTimeMillis, mEndTimeMillis); if (uidOp.getPackageCount() == 0) { mHistoricalUidOps.removeAt(i); } @@ -5013,6 +5095,16 @@ public class AppOpsManager { /** @hide */ @TestApi + public void addDiscreteAccess(int opCode, int uid, @NonNull String packageName, + @Nullable String attributionTag, @UidState int uidState, @OpFlags int opFlag, + long discreteAccessTime, long discreteAccessDuration) { + getOrCreateHistoricalUidOps(uid).addDiscreteAccess(opCode, packageName, attributionTag, + uidState, opFlag, discreteAccessTime, discreteAccessDuration); + }; + + + /** @hide */ + @TestApi public void offsetBeginAndEndTime(long offsetMillis) { mBeginTimeMillis += offsetMillis; mEndTimeMillis += offsetMillis; @@ -5288,7 +5380,8 @@ public class AppOpsManager { private void filter(@Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, - double fractionToRemove) { + @OpHistoryFlags int historyFilter, double fractionToRemove, long beginTimeMillis, + long endTimeMillis) { final int packageCount = getPackageCount(); for (int i = packageCount - 1; i >= 0; i--) { final HistoricalPackageOps packageOps = getPackageOpsAt(i); @@ -5296,7 +5389,8 @@ public class AppOpsManager { packageOps.getPackageName())) { mHistoricalPackageOps.removeAt(i); } else { - packageOps.filter(attributionTag, opNames, filter, fractionToRemove); + packageOps.filter(attributionTag, opNames, filter, historyFilter, + fractionToRemove, beginTimeMillis, endTimeMillis); if (packageOps.getAttributedOpsCount() == 0) { mHistoricalPackageOps.removeAt(i); } @@ -5336,6 +5430,13 @@ public class AppOpsManager { opCode, attributionTag, uidState, flags, increment); } + private void addDiscreteAccess(int opCode, @NonNull String packageName, + @Nullable String attributionTag, @UidState int uidState, + @OpFlags int flag, long discreteAccessTime, long discreteAccessDuration) { + getOrCreateHistoricalPackageOps(packageName).addDiscreteAccess(opCode, attributionTag, + uidState, flag, discreteAccessTime, discreteAccessDuration); + }; + /** * @return The UID for which the data is related. */ @@ -5540,7 +5641,8 @@ public class AppOpsManager { } private void filter(@Nullable String attributionTag, @Nullable String[] opNames, - @HistoricalOpsRequestFilter int filter, double fractionToRemove) { + @HistoricalOpsRequestFilter int filter, @OpHistoryFlags int historyFilter, + double fractionToRemove, long beginTimeMillis, long endTimeMillis) { final int attributionCount = getAttributedOpsCount(); for (int i = attributionCount - 1; i >= 0; i--) { final AttributedHistoricalOps attributionOps = getAttributedOpsAt(i); @@ -5548,7 +5650,8 @@ public class AppOpsManager { attributionOps.getTag())) { mAttributedHistoricalOps.removeAt(i); } else { - attributionOps.filter(opNames, filter, fractionToRemove); + attributionOps.filter(opNames, filter, historyFilter, fractionToRemove, + beginTimeMillis, endTimeMillis); if (attributionOps.getOpCount() == 0) { mAttributedHistoricalOps.removeAt(i); } @@ -5593,6 +5696,13 @@ public class AppOpsManager { opCode, uidState, flags, increment); } + private void addDiscreteAccess(int opCode, @Nullable String attributionTag, + @UidState int uidState, @OpFlags int flag, long discreteAccessTime, + long discreteAccessDuration) { + getOrCreateAttributedHistoricalOps(attributionTag).addDiscreteAccess(opCode, uidState, + flag, discreteAccessTime, discreteAccessDuration); + } + /** * Gets the package name which the data represents. * @@ -5870,7 +5980,8 @@ public class AppOpsManager { } private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, - double scaleFactor) { + @OpHistoryFlags int historyFilter, double scaleFactor, long beginTimeMillis, + long endTimeMillis) { final int opCount = getOpCount(); for (int i = opCount - 1; i >= 0; i--) { final HistoricalOp op = mHistoricalOps.valueAt(i); @@ -5878,7 +5989,7 @@ public class AppOpsManager { op.getOpName())) { mHistoricalOps.removeAt(i); } else { - op.filter(scaleFactor); + op.filter(historyFilter, scaleFactor, beginTimeMillis, endTimeMillis); } } } @@ -5909,6 +6020,12 @@ public class AppOpsManager { getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, flags, increment); } + private void addDiscreteAccess(int opCode, @UidState int uidState, @OpFlags int flag, + long discreteAccessTime, long discreteAccessDuration) { + getOrCreateHistoricalOp(opCode).addDiscreteAccess(uidState,flag, discreteAccessTime, + discreteAccessDuration); + } + /** * Gets number historical app ops. * @@ -5970,8 +6087,6 @@ public class AppOpsManager { return op; } - - // Code below generated by codegen v1.0.14. // // DO NOT MODIFY! @@ -6121,6 +6236,9 @@ public class AppOpsManager { private @Nullable LongSparseLongArray mRejectCount; private @Nullable LongSparseLongArray mAccessDuration; + /** Discrete Ops for this Op */ + private @Nullable List<AttributedOpEntry> mDiscreteAccesses; + /** @hide */ public HistoricalOp(int op) { mOp = op; @@ -6137,6 +6255,12 @@ public class AppOpsManager { if (other.mAccessDuration != null) { mAccessDuration = other.mAccessDuration.clone(); } + final int historicalOpCount = other.getDiscreteAccessCount(); + for (int i = 0; i < historicalOpCount; i++) { + final AttributedOpEntry origOp = other.getDiscreteAccessAt(i); + final AttributedOpEntry cloneOp = new AttributedOpEntry(origOp); + getOrCreateDiscreteAccesses().add(cloneOp); + } } private HistoricalOp(@NonNull Parcel parcel) { @@ -6144,22 +6268,45 @@ public class AppOpsManager { mAccessCount = readLongSparseLongArrayFromParcel(parcel); mRejectCount = readLongSparseLongArrayFromParcel(parcel); mAccessDuration = readLongSparseLongArrayFromParcel(parcel); + mDiscreteAccesses = readDiscreteAccessArrayFromParcel(parcel); } - private void filter(double scaleFactor) { - scale(mAccessCount, scaleFactor); - scale(mRejectCount, scaleFactor); - scale(mAccessDuration, scaleFactor); + private void filter(@OpHistoryFlags int historyFlag, double scaleFactor, + long beginTimeMillis, long endTimeMillis) { + if ((historyFlag & HISTORY_FLAG_AGGREGATE) == 0) { + mAccessCount = null; + mRejectCount = null; + mAccessDuration = null; + } else { + scale(mAccessCount, scaleFactor); + scale(mRejectCount, scaleFactor); + scale(mAccessDuration, scaleFactor); + } + if ((historyFlag & HISTORY_FLAG_DISCRETE) == 0) { + mDiscreteAccesses = null; + return; + } + final int discreteOpCount = getDiscreteAccessCount(); + for (int i = discreteOpCount - 1; i >= 0; i--) { + final AttributedOpEntry op = mDiscreteAccesses.get(i); + long opBeginTime = op.getLastAccessTime(OP_FLAGS_ALL); + long opEndTime = opBeginTime + op.getLastDuration(OP_FLAGS_ALL); + opEndTime = max(opBeginTime, opEndTime); + if (opEndTime < beginTimeMillis || opBeginTime > endTimeMillis) { + mDiscreteAccesses.remove(i); + } + } } private boolean isEmpty() { return !hasData(mAccessCount) && !hasData(mRejectCount) - && !hasData(mAccessDuration); + && !hasData(mAccessDuration) + && (mDiscreteAccesses == null); } private boolean hasData(@NonNull LongSparseLongArray array) { - return (array != null && array.size() > 0); + return array != null && array.size() > 0; } private @Nullable HistoricalOp splice(double fractionToRemove) { @@ -6191,6 +6338,32 @@ public class AppOpsManager { merge(this::getOrCreateAccessCount, other.mAccessCount); merge(this::getOrCreateRejectCount, other.mRejectCount); merge(this::getOrCreateAccessDuration, other.mAccessDuration); + + if (other.mDiscreteAccesses == null) { + return; + } + if (mDiscreteAccesses == null) { + mDiscreteAccesses = new ArrayList(other.mDiscreteAccesses); + return; + } + List<AttributedOpEntry> historicalDiscreteAccesses = new ArrayList<>(); + final int otherHistoricalOpCount = other.getDiscreteAccessCount(); + final int historicalOpCount = getDiscreteAccessCount(); + int i = 0; + int j = 0; + while (i < otherHistoricalOpCount || j < historicalOpCount) { + if (i == otherHistoricalOpCount) { + historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++)); + } else if (j == historicalOpCount) { + historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++)); + } else if (mDiscreteAccesses.get(j).getLastAccessTime(OP_FLAGS_ALL) + < other.mDiscreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL)) { + historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++)); + } else { + historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++)); + } + } + mDiscreteAccesses = historicalDiscreteAccesses; } private void increaseAccessCount(@UidState int uidState, @OpFlags int flags, @@ -6218,6 +6391,23 @@ public class AppOpsManager { } } + private void addDiscreteAccess(@UidState int uidState, @OpFlags int flag, + long discreteAccessTime, long discreteAccessDuration) { + List<AttributedOpEntry> discreteAccesses = getOrCreateDiscreteAccesses(); + LongSparseArray<NoteOpEvent> accessEvents = new LongSparseArray<>(); + long key = makeKey(uidState, flag); + NoteOpEvent note = new NoteOpEvent(discreteAccessTime, discreteAccessDuration, null); + accessEvents.append(key, note); + AttributedOpEntry access = new AttributedOpEntry(mOp, false, accessEvents, null); + for (int i = discreteAccesses.size() - 1; i >= 0; i--) { + if (discreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL) < discreteAccessTime) { + discreteAccesses.add(i + 1, access); + return; + } + } + discreteAccesses.add(0, access); + } + /** * Gets the op name. * @@ -6233,6 +6423,33 @@ public class AppOpsManager { } /** + * Gets number of discrete historical app ops. + * + * @return The number historical app ops. + * @see #getOpAt(int) + */ + public @IntRange(from = 0) int getDiscreteAccessCount() { + if (mDiscreteAccesses == null) { + return 0; + } + return mDiscreteAccesses.size(); + } + + /** + * Gets the historical op at a given index. + * + * @param index The index to lookup. + * @return The op at the given index. + * @see #getOpCount() + */ + public @NonNull AttributedOpEntry getDiscreteAccessAt(@IntRange(from = 0) int index) { + if (mDiscreteAccesses == null) { + throw new IndexOutOfBoundsException(); + } + return mDiscreteAccesses.get(index); + } + + /** * Gets the number times the op was accessed (performed) in the foreground. * * @param flags The flags which are any combination of @@ -6251,6 +6468,25 @@ public class AppOpsManager { } /** + * Gets the discrete events the op was accessed (performed) in the foreground. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The list of discrete ops accessed in the foreground. + * + * @see #getBackgroundDiscreteAccesses(int) + * @see #getDiscreteAccesses(int, int, int) + */ + @NonNull + public List<AttributedOpEntry> getForegroundDiscreteAccesses(@OpFlags int flags) { + return listForFlagsInStates(mDiscreteAccesses, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** * Gets the number times the op was accessed (performed) in the background. * * @param flags The flags which are any combination of @@ -6269,6 +6505,25 @@ public class AppOpsManager { } /** + * Gets the discrete events the op was accessed (performed) in the background. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The list of discrete ops accessed in the background. + * + * @see #getForegroundDiscreteAccesses(int) + * @see #getDiscreteAccesses(int, int, int) + */ + @NonNull + public List<AttributedOpEntry> getBackgroundDiscreteAccesses(@OpFlags int flags) { + return listForFlagsInStates(mDiscreteAccesses, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** * Gets the number times the op was accessed (performed) for a * range of uid states. * @@ -6294,6 +6549,26 @@ public class AppOpsManager { } /** + * Gets the discrete events the op was accessed (performed) for a + * range of uid states. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The discrete the op was accessed in the background. + * + * @see #getBackgroundDiscreteAccesses(int) + * @see #getForegroundDiscreteAccesses(int) + */ + @NonNull + public List<AttributedOpEntry> getDiscreteAccesses(@UidState int fromUidState, + @UidState int toUidState, @OpFlags int flags) { + return listForFlagsInStates(mDiscreteAccesses, fromUidState, toUidState, flags); + } + + /** * Gets the number times the op was rejected in the foreground. * * @param flags The flags which are any combination of @@ -6427,6 +6702,7 @@ public class AppOpsManager { writeLongSparseLongArrayToParcel(mAccessCount, parcel); writeLongSparseLongArrayToParcel(mRejectCount, parcel); writeLongSparseLongArrayToParcel(mAccessDuration, parcel); + writeDiscreteAccessArrayToParcel(mDiscreteAccesses, parcel); } @Override @@ -6447,7 +6723,11 @@ public class AppOpsManager { if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) { return false; } - return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration); + if (!equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration)) { + return false; + } + return mDiscreteAccesses == null ? (other.mDiscreteAccesses == null ? true + : false) : mDiscreteAccesses.equals(other.mDiscreteAccesses); } @Override @@ -6456,6 +6736,7 @@ public class AppOpsManager { result = 31 * result + Objects.hashCode(mAccessCount); result = 31 * result + Objects.hashCode(mRejectCount); result = 31 * result + Objects.hashCode(mAccessDuration); + result = 31 * result + Objects.hashCode(mDiscreteAccesses); return result; } @@ -6484,6 +6765,13 @@ public class AppOpsManager { return mAccessDuration; } + private @NonNull List<AttributedOpEntry> getOrCreateDiscreteAccesses() { + if (mDiscreteAccesses == null) { + mDiscreteAccesses = new ArrayList<>(); + } + return mDiscreteAccesses; + } + /** * Multiplies the entries in the array with the passed in scale factor and * rounds the result at up 0.5 boundary. @@ -6574,6 +6862,32 @@ public class AppOpsManager { } /** + * Returns list of events filtered by UidState and UID flags. + * + * @param accesses The events list. + * @param beginUidState The beginning UID state (inclusive). + * @param endUidState The end UID state (inclusive). + * @param flags The UID flags. + * @return filtered list of events. + */ + private static List<AttributedOpEntry> listForFlagsInStates(List<AttributedOpEntry> accesses, + @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) { + List<AttributedOpEntry> result = new ArrayList<>(); + if (accesses == null) { + return result; + } + int nAccesses = accesses.size(); + for (int i = 0; i < nAccesses; i++) { + AttributedOpEntry entry = accesses.get(i); + if (entry.getLastAccessTime(beginUidState, endUidState, flags) == -1) { + continue; + } + result.add(entry); + } + return result; + } + + /** * Callback for notification of changes to operation state. */ public interface OnOpChangedListener { @@ -6796,8 +7110,9 @@ public class AppOpsManager { Objects.requireNonNull(callback, "callback cannot be null"); try { mService.getHistoricalOps(request.mUid, request.mPackageName, request.mAttributionTag, - request.mOpNames, request.mFilter, request.mBeginTimeMillis, - request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> { + request.mOpNames, request.mHistoryFlags, request.mFilter, + request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags, + new RemoteCallback((result) -> { final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); try { @@ -6835,9 +7150,9 @@ public class AppOpsManager { Objects.requireNonNull(callback, "callback cannot be null"); try { mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName, - request.mAttributionTag, request.mOpNames, request.mFilter, - request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags, - new RemoteCallback((result) -> { + request.mAttributionTag, request.mOpNames, request.mHistoryFlags, + request.mFilter, request.mBeginTimeMillis, request.mEndTimeMillis, + request.mFlags, new RemoteCallback((result) -> { final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); try { @@ -9072,6 +9387,32 @@ public class AppOpsManager { return array; } + private static void writeDiscreteAccessArrayToParcel( + @Nullable List<AttributedOpEntry> array, @NonNull Parcel parcel) { + if (array != null) { + final int size = array.size(); + parcel.writeInt(size); + for (int i = 0; i < size; i++) { + array.get(i).writeToParcel(parcel, 0); + } + } else { + parcel.writeInt(-1); + } + } + + private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel( + @NonNull Parcel parcel) { + final int size = parcel.readInt(); + if (size < 0) { + return null; + } + final List<AttributedOpEntry> array = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + array.add(new AttributedOpEntry(parcel)); + } + return array; + } + /** * Collects the keys from an array to the result creating the result if needed. * diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index bc798136f2e3..fd56c449012f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -24,6 +24,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ContextParams; import android.content.AutofillOptions; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -2606,6 +2607,12 @@ class ContextImpl extends Context { compatInfo, mClassLoader, loaders); } + @NonNull + @Override + public Context createContext(@NonNull ContextParams contextParams) { + return this; + } + @Override public @NonNull Context createAttributionContext(@Nullable String attributionTag) { return new ContextImpl(this, mMainThread, mPackageInfo, attributionTag, mSplitName, diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 3a8172ea98b8..ef0dcabbe111 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -706,7 +706,7 @@ interface IActivityManager { boolean stopProfile(int userId); /** Called by PendingIntent.queryIntentComponents() */ - List<ResolveInfo> queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags); + ParceledListSlice queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags); int getUidProcessCapabilities(int uid, in String callingPackage); } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 8d1076ea1277..b2184fe65887 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1247,7 +1247,8 @@ public class Instrumentation { info, title, parent, id, (Activity.NonConfigurationInstances)lastNonConfigurationInstance, new Configuration(), null /* referrer */, null /* voiceInteractor */, - null /* window */, null /* activityConfigCallback */, null /*assistToken*/); + null /* window */, null /* activityConfigCallback */, null /*assistToken*/, + null /*shareableActivityToken*/); return activity; } diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java index 74e61250f109..6b5f19a52e33 100644 --- a/core/java/android/app/LocalActivityManager.java +++ b/core/java/android/app/LocalActivityManager.java @@ -158,7 +158,7 @@ public class LocalActivityManager { r.activityInfo = mActivityThread.resolveActivityInfo(r.intent); } r.activity = mActivityThread.startActivityNow( - mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance, r); + mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance, r, r); if (r.activity == null) { return; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8167622ff13c..bc24e9767944 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -24,6 +24,7 @@ import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; import static java.util.Objects.requireNonNull; +import android.annotation.AttrRes; import android.annotation.ColorInt; import android.annotation.ColorRes; import android.annotation.DimenRes; @@ -98,6 +99,7 @@ import android.widget.RemoteViews; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; @@ -3649,11 +3651,6 @@ public class Notification implements Parcelable private int mCachedContrastColorIsFor = COLOR_INVALID; /** - * A neutral color color that can be used for icons. - */ - private int mNeutralColor = COLOR_INVALID; - - /** * Caches an instance of StandardTemplateParams. Note that this may have been used before, * so make sure to call {@link StandardTemplateParams#reset()} before using it. */ @@ -3666,6 +3663,7 @@ public class Notification implements Parcelable private boolean mRebuildStyledRemoteViews; private boolean mTintActionButtons; + private boolean mTintWithThemeAccent; private boolean mInNightMode; /** @@ -3701,6 +3699,7 @@ public class Notification implements Parcelable mContext = context; Resources res = mContext.getResources(); mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); + mTintWithThemeAccent = res.getBoolean(R.bool.config_tintNotificationsWithTheme); if (res.getBoolean(R.bool.config_enableNightMode)) { Configuration currentConfig = res.getConfiguration(); @@ -4891,12 +4890,10 @@ public class Notification implements Parcelable } private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { - // TODO(b/180334837): Get buy-in on this color, or make sure to give this the - // accent color, while still accommodating the colorized state. contentView.setDrawableTint( R.id.phishing_alert, false /* targetBackground */, - getPrimaryTextColor(p), + getErrorColor(p), PorterDuff.Mode.SRC_ATOP); } @@ -4943,7 +4940,7 @@ public class Notification implements Parcelable contentView.setDrawableTint( R.id.alerted_icon, false /* targetBackground */, - getNeutralColor(p), + getHeaderIconColor(p), PorterDuff.Mode.SRC_ATOP); } @@ -5057,10 +5054,9 @@ public class Notification implements Parcelable return text; } - private void setTextViewColorPrimary(RemoteViews contentView, int id, + private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p) { - ensureColors(p); - contentView.setTextColor(id, mPrimaryTextColor); + contentView.setTextColor(id, getPrimaryTextColor(p)); } private boolean hasForegroundColor() { @@ -5068,53 +5064,34 @@ public class Notification implements Parcelable } /** - * Return the primary text color using the existing template params - * @hide - */ - @VisibleForTesting - public int getPrimaryTextColor() { - return getPrimaryTextColor(mParams); - } - - /** * @param p the template params to inflate this with * @return the primary text color * @hide */ @VisibleForTesting - public int getPrimaryTextColor(StandardTemplateParams p) { + public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { ensureColors(p); return mPrimaryTextColor; } /** - * Return the secondary text color using the existing template params - * @hide - */ - @VisibleForTesting - public int getSecondaryTextColor() { - return getSecondaryTextColor(mParams); - } - - /** * @param p the template params to inflate this with * @return the secondary text color * @hide */ @VisibleForTesting - public int getSecondaryTextColor(StandardTemplateParams p) { + public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { ensureColors(p); return mSecondaryTextColor; } - private void setTextViewColorSecondary(RemoteViews contentView, int id, + private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p) { - ensureColors(p); - contentView.setTextColor(id, mSecondaryTextColor); + contentView.setTextColor(id, getSecondaryTextColor(p)); } private void ensureColors(StandardTemplateParams p) { - int backgroundColor = getBackgroundColor(p); + int backgroundColor = getUnresolvedBackgroundColor(p); if (mPrimaryTextColor == COLOR_INVALID || mSecondaryTextColor == COLOR_INVALID || mTextColorsAreForBackground != backgroundColor) { @@ -5217,7 +5194,7 @@ public class Notification implements Parcelable R.id.progress, ColorStateList.valueOf(mContext.getColor( R.color.notification_progress_background_color))); if (getRawColor(p) != COLOR_DEFAULT) { - int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); + int color = getAccentColor(p); ColorStateList colorStateList = ColorStateList.valueOf(color); contentView.setProgressTintList(R.id.progress, colorStateList); contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); @@ -5326,11 +5303,18 @@ public class Notification implements Parcelable } private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { - int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p); - contentView.setDrawableTint(R.id.expand_button, false, color, - PorterDuff.Mode.SRC_ATOP); - contentView.setInt(R.id.expand_button, "setOriginalNotificationColor", - color); + // set default colors + int textColor = getPrimaryTextColor(p); + int pillColor = getProtectionColor(p); + 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) { + textColor = getBackgroundColor(p); + pillColor = getAccentColor(p); + } + contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); + contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); } private void bindHeaderChronometerAndTime(RemoteViews contentView, @@ -5461,11 +5445,7 @@ public class Notification implements Parcelable } contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); - if (isColorized(p)) { - setTextViewColorPrimary(contentView, R.id.app_name_text, p); - } else { - contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); - } + contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); return true; } @@ -5555,6 +5535,10 @@ public class Notification implements Parcelable resetStandardTemplateWithActions(big); bindSnoozeAction(big, p); + // color the snooze and bubble actions with the theme color + ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); + big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); + big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); boolean validRemoteInput = false; @@ -5604,8 +5588,7 @@ public class Notification implements Parcelable showSpinner ? View.VISIBLE : View.GONE); big.setProgressIndeterminateTintList( R.id.notification_material_reply_progress, - ColorStateList.valueOf( - isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); + ColorStateList.valueOf(getAccentColor(p))); if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) && p.maxRemoteInputHistory > 1) { @@ -6021,14 +6004,14 @@ public class Notification implements Parcelable // change the background bgColor CharSequence title = action.title; ColorStateList[] outResultColor = new ColorStateList[1]; - int background = resolveBackgroundColor(p); + int background = getBackgroundColor(p); if (isLegacy()) { title = ContrastColorUtil.clearColorSpans(title); } else { title = ensureColorSpanContrast(title, background, outResultColor); } button.setTextViewText(R.id.action0, processTextSpans(title)); - int textColor = getPrimaryTextColor(p); + final int textColor; boolean hasColorOverride = outResultColor[0] != null; if (hasColorOverride) { // There's a span spanning the full text, let's take it and use it as the @@ -6036,9 +6019,11 @@ public class Notification implements Parcelable background = outResultColor[0].getDefaultColor(); textColor = ContrastColorUtil.resolvePrimaryColor(mContext, background, mInNightMode); - } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p) - && mTintActionButtons && !mInNightMode) { - textColor = resolveContrastColor(p); + } else if (mTintActionButtons && !mInNightMode + && getRawColor(p) != COLOR_DEFAULT && !isColorized(p)) { + textColor = getAccentColor(p); + } else { + textColor = getPrimaryTextColor(p); } button.setTextColor(R.id.action0, textColor); // We only want about 20% alpha for the ripple @@ -6056,11 +6041,7 @@ public class Notification implements Parcelable } else { button.setTextViewText(R.id.action0, processTextSpans( processLegacyText(action.title))); - if (isColorized(p)) { - setTextViewColorPrimary(button, R.id.action0, p); - } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) { - button.setTextColor(R.id.action0, resolveContrastColor(p)); - } + button.setTextColor(R.id.action0, getStandardActionColor(p)); } // CallStyle notifications add action buttons which don't actually exist in mActions, // so we have to omit the index in that case. @@ -6170,9 +6151,9 @@ public class Notification implements Parcelable private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, StandardTemplateParams p) { boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); - int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); + int color = getSmallIconColor(p); contentView.setInt(R.id.icon, "setBackgroundColor", - resolveBackgroundColor(p)); + getBackgroundColor(p)); contentView.setInt(R.id.icon, "setOriginalIconColor", colorable ? color : COLOR_INVALID); } @@ -6187,7 +6168,7 @@ public class Notification implements Parcelable if (largeIcon != null && isLegacy() && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { // resolve color will fall back to the default when legacy - int color = resolveContrastColor(p); + int color = getContrastColor(p); contentView.setInt(R.id.icon, "setOriginalIconColor", color); } } @@ -6198,14 +6179,94 @@ public class Notification implements Parcelable } } - int resolveContrastColor(StandardTemplateParams p) { + /** + * Gets the standard action button color + */ + private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { + return mTintActionButtons || isColorized(p) ? getAccentColor(p) : getNeutralColor(p); + } + + /** + * Gets a neutral color that can be used for icons or similar that should not stand out. + */ + private @ColorInt int getHeaderIconColor(StandardTemplateParams p) { + return isColorized(p) ? getSecondaryTextColor(p) : getNeutralColor(p); + } + + /** + * Gets the foreground color of the small icon. If the notification is colorized, this + * is the primary text color, otherwise it's the contrast-adjusted app-provided color. + */ + private @ColorInt int getSmallIconColor(StandardTemplateParams p) { + return isColorized(p) ? getPrimaryTextColor(p) : getContrastColor(p); + } + + /** + * Gets the accent color for colored UI elements. If we're tinting with the theme + * accent, this is the theme accent color, otherwise this would be identical to + * {@link #getSmallIconColor(StandardTemplateParams)}. + */ + private @ColorInt int getAccentColor(StandardTemplateParams p) { + if (isColorized(p)) { + return getPrimaryTextColor(p); + } + if (mTintWithThemeAccent) { + int color = obtainThemeColor(R.attr.colorAccent, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + return getContrastColor(p); + } + + /** + * Gets the "surface protection" color from the theme, or a variant of the normal background + * color when colorized, or when not using theme color tints. + */ + private @ColorInt int getProtectionColor(StandardTemplateParams p) { + if (mTintWithThemeAccent && !isColorized(p)) { + int color = obtainThemeColor(R.attr.colorBackgroundFloating, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + // TODO(b/181048615): What color should we use for the expander pill when colorized + return ColorUtils.blendARGB(getPrimaryTextColor(p), getBackgroundColor(p), 0.8f); + } + + /** + * Gets the theme's error color, or the primary text color for colorized notifications. + */ + private @ColorInt int getErrorColor(StandardTemplateParams p) { + if (!isColorized(p)) { + int color = obtainThemeColor(R.attr.colorError, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + return getPrimaryTextColor(p); + } + + /** + * Gets the theme's background color + */ + private @ColorInt int getDefaultBackgroundColor() { + return obtainThemeColor(R.attr.colorBackground, + mInNightMode ? Color.BLACK : Color.WHITE); + } + + /** + * Gets the contrast-adjusted version of the color provided by the app. + */ + private @ColorInt int getContrastColor(StandardTemplateParams p) { int rawColor = getRawColor(p); if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { return mCachedContrastColor; } int color; - int background = obtainBackgroundColor(); + // TODO: Maybe use getBackgroundColor(p) instead -- but doing so could break the cache + int background = getDefaultBackgroundColor(); if (rawColor == COLOR_DEFAULT) { ensureColors(p); color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); @@ -6224,28 +6285,29 @@ public class Notification implements Parcelable /** * Return the raw color of this Notification, which doesn't necessarily satisfy contrast. * - * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color + * @see #getContrastColor(StandardTemplateParams) for the contrasted color * @param p the template params to inflate this with */ - private int getRawColor(StandardTemplateParams p) { + private @ColorInt int getRawColor(StandardTemplateParams p) { if (p.forceDefaultColor) { return COLOR_DEFAULT; } return mN.color; } - int resolveNeutralColor() { - if (mNeutralColor != COLOR_INVALID) { - return mNeutralColor; - } - int background = obtainBackgroundColor(); - mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, + /** + * Gets a neutral palette color; this is a contrast-satisfied version of the default color. + * @param p the template params to inflate this with + */ + private @ColorInt int getNeutralColor(StandardTemplateParams p) { + int background = getBackgroundColor(p); + int neutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); - if (Color.alpha(mNeutralColor) < 255) { + if (Color.alpha(neutralColor) < 255) { // alpha doesn't go well for color filters, so let's blend it manually - mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background); + neutralColor = ContrastColorUtil.compositeColors(neutralColor, background); } - return mNeutralColor; + return neutralColor; } /** @@ -6389,8 +6451,11 @@ public class Notification implements Parcelable return mN; } - private @ColorInt int obtainBackgroundColor() { - int defaultColor = mInNightMode ? Color.BLACK : Color.WHITE; + /** + * Returns the color for the given Theme.DeviceDefault.DayNight attribute, or + * defValue if that could not be completed + */ + private @ColorInt int obtainThemeColor(@AttrRes int attrRes, @ColorInt int defaultColor) { Resources.Theme theme = mContext.getTheme(); if (theme == null) { // Running unit tests with mocked context @@ -6398,7 +6463,7 @@ public class Notification implements Parcelable } theme = new ContextThemeWrapper(mContext, R.style.Theme_DeviceDefault_DayNight) .getTheme(); - TypedArray ta = theme.obtainStyledAttributes(new int[]{R.attr.colorBackground}); + TypedArray ta = theme.obtainStyledAttributes(new int[]{attrRes}); if (ta == null) { return defaultColor; } @@ -6517,42 +6582,30 @@ public class Notification implements Parcelable return R.layout.notification_material_action_tombstone; } - private int getBackgroundColor(StandardTemplateParams p) { - if (isColorized(p)) { - return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); - } else { - return COLOR_DEFAULT; - } - } - /** - * Gets a neutral color that can be used for icons or similar that should not stand out. - * @param p the template params to inflate this with + * Gets the background color, with {@link #COLOR_DEFAULT} being a valid return value, + * which must be resolved by the caller before being used. */ - private int getNeutralColor(StandardTemplateParams p) { + private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) { if (isColorized(p)) { - return getSecondaryTextColor(p); + return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); } else { - return resolveNeutralColor(); + return COLOR_DEFAULT; } } /** - * Same as getBackgroundColor but also resolved the default color to the background. - * @param p the template params to inflate this with + * Same as {@link #getUnresolvedBackgroundColor(StandardTemplateParams)} except that it + * also resolves the default color to the background. */ - private int resolveBackgroundColor(StandardTemplateParams p) { - int backgroundColor = getBackgroundColor(p); + private @ColorInt int getBackgroundColor(StandardTemplateParams p) { + int backgroundColor = getUnresolvedBackgroundColor(p); if (backgroundColor == COLOR_DEFAULT) { - backgroundColor = obtainBackgroundColor(); + backgroundColor = getDefaultBackgroundColor(); } return backgroundColor; } - private boolean shouldTintActionButtons() { - return mTintActionButtons; - } - private boolean textColorsNeedInversion() { if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { return false; @@ -6570,7 +6623,7 @@ public class Notification implements Parcelable * * @hide */ - public void setColorPalette(int backgroundColor, int foregroundColor) { + public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) { mBackgroundColor = backgroundColor; mForegroundColor = foregroundColor; mTextColorsAreForBackground = COLOR_INVALID; @@ -8200,16 +8253,14 @@ public class Notification implements Parcelable TypedValue.COMPLEX_UNIT_DIP); } contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", - mBuilder.isColorized(p) - ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p)); + mBuilder.getSmallIconColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", mBuilder.getPrimaryTextColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", mBuilder.getSecondaryTextColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setNotificationBackgroundColor", - mBuilder.resolveBackgroundColor(p)); + mBuilder.getBackgroundColor(p)); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", @@ -8964,14 +9015,7 @@ public class Notification implements Parcelable // If the action buttons should not be tinted, then just use the default // notification color. Otherwise, just use the passed-in color. - Resources resources = mBuilder.mContext.getResources(); - Configuration currentConfig = resources.getConfiguration(); - boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p) - ? getActionColor(p) - : ContrastColorUtil.resolveColor(mBuilder.mContext, - Notification.COLOR_DEFAULT, inNightMode); + int tintColor = mBuilder.getStandardActionColor(p); container.setDrawableTint(buttonId, false, tintColor, PorterDuff.Mode.SRC_ATOP); @@ -9027,11 +9071,6 @@ public class Notification implements Parcelable return view; } - private int getActionColor(StandardTemplateParams p) { - return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p); - } - private RemoteViews makeMediaBigContentView() { final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); // Dont add an expanded view if there is no more content to be revealed @@ -9373,7 +9412,6 @@ public class Notification implements Parcelable .hideLargeIcon(true) .text(text) .summaryText(mBuilder.processLegacyText(mVerificationText)); - // TODO(b/179178086): hide the snooze button RemoteViews contentView = mBuilder.applyStandardTemplate( mBuilder.getCallLayoutResource(), p, null /* result */); @@ -9390,11 +9428,9 @@ public class Notification implements Parcelable // Bind some custom CallLayout properties contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", - mBuilder.isColorized(p) - ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p)); + mBuilder.getSmallIconColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, - "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p)); + "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); contentView.setBundle(R.id.status_bar_latest_event_content, "setData", diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 1ff64dbe6d2e..e0e9b62d3809 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -73,6 +73,7 @@ per-file ClientTransactionHandler.java = file:/services/core/java/com/android/se per-file Fragment.java = file:/services/core/java/com/android/server/wm/OWNERS per-file *Task* = file:/services/core/java/com/android/server/wm/OWNERS per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS +per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS # TODO(b/174932174): determine the ownership of KeyguardManager.java diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 549bd4b9fe6a..009c9366b1d6 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -26,7 +26,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemApi.Client; import android.annotation.TestApi; @@ -41,6 +40,7 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager.ResolveInfoFlags; +import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.os.Build; import android.os.Bundle; @@ -60,6 +60,7 @@ import com.android.internal.os.IResultReceiver; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -1239,14 +1240,17 @@ public final class PendingIntent implements Parcelable { * @param flags MATCH_* flags from {@link android.content.pm.PackageManager}. * @hide */ - @SuppressLint("NullableCollection") @RequiresPermission(permission.GET_INTENT_SENDER_INTENT) @SystemApi(client = Client.MODULE_LIBRARIES) @TestApi - public @Nullable List<ResolveInfo> queryIntentComponents(@ResolveInfoFlags int flags) { + public @NonNull List<ResolveInfo> queryIntentComponents(@ResolveInfoFlags int flags) { try { - return ActivityManager.getService() + ParceledListSlice<ResolveInfo> parceledList = ActivityManager.getService() .queryIntentComponentsForIntentSender(mTarget, flags); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index ea7eab2a2877..358ce6a83a21 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -202,7 +202,7 @@ public final class PictureInPictureParams implements Parcelable { } if (in.readInt() != 0) { mUserActions = new ArrayList<>(); - in.readParcelableList(mUserActions, RemoteAction.class.getClassLoader()); + in.readTypedList(mUserActions, RemoteAction.CREATOR); } if (in.readInt() != 0) { mSourceRectHint = Rect.CREATOR.createFromParcel(in); @@ -386,7 +386,7 @@ public final class PictureInPictureParams implements Parcelable { } if (mUserActions != null) { out.writeInt(1); - out.writeParcelableList(mUserActions, 0); + out.writeTypedList(mUserActions, 0); } else { out.writeInt(0); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e16e40b6d572..43c14a99b221 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -71,7 +71,6 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.pm.verify.domain.DomainVerificationManager; -import android.content.pm.verify.domain.DomainVerificationManagerImpl; import android.content.pm.verify.domain.IDomainVerificationManager; import android.content.res.Resources; import android.content.rollback.RollbackManagerFrameworkInitializer; @@ -1422,7 +1421,6 @@ public final class SystemServiceRegistry { } }); - // TODO(b/159952358): Only register this service for the domain verification agent? registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class, new CachedServiceFetcher<DomainVerificationManager>() { @Override @@ -1432,7 +1430,7 @@ public final class SystemServiceRegistry { Context.DOMAIN_VERIFICATION_SERVICE); IDomainVerificationManager service = IDomainVerificationManager.Stub.asInterface(binder); - return new DomainVerificationManagerImpl(context, service); + return new DomainVerificationManager(context, service); } }); diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index 3abba43ae0a8..0a8a73404a7b 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -16,9 +16,9 @@ package android.app; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -35,6 +35,8 @@ import com.android.internal.graphics.palette.Palette; import com.android.internal.util.ContrastColorUtil; import java.io.FileOutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -50,6 +52,13 @@ import java.util.stream.Collectors; * or {@link WallpaperColors#getTertiaryColor()}. */ public final class WallpaperColors implements Parcelable { + /** + * @hide + */ + @IntDef(prefix = "HINT_", value = {HINT_SUPPORTS_DARK_TEXT, HINT_SUPPORTS_DARK_THEME}, + flag = true) + @Retention(RetentionPolicy.SOURCE) + public @interface ColorsHints {} private static final boolean DEBUG_DARK_PIXELS = false; @@ -57,18 +66,14 @@ public final class WallpaperColors implements Parcelable { * Specifies that dark text is preferred over the current wallpaper for best presentation. * <p> * eg. A launcher may set its text color to black if this flag is specified. - * @hide */ - @SystemApi public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0; /** * Specifies that dark theme is preferred over the current wallpaper for best presentation. * <p> * eg. A launcher may set its drawer color to black if this flag is specified. - * @hide */ - @SystemApi public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1; /** @@ -229,15 +234,12 @@ public final class WallpaperColors implements Parcelable { * @param primaryColor Primary color. * @param secondaryColor Secondary color. * @param tertiaryColor Tertiary color. - * @param colorHints A combination of WallpaperColor hints. - * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @param colorHints A combination of color hints. * @see WallpaperColors#fromBitmap(Bitmap) * @see WallpaperColors#fromDrawable(Drawable) - * @hide */ - @SystemApi public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, - @Nullable Color tertiaryColor, int colorHints) { + @Nullable Color tertiaryColor, @ColorsHints int colorHints) { if (primaryColor == null) { throw new IllegalArgumentException("Primary color should never be null."); @@ -268,13 +270,14 @@ public final class WallpaperColors implements Parcelable { * * @param populationByColor Map with keys of colors, and value representing the number of * occurrences of color in the wallpaper. - * @param colorHints A combination of WallpaperColor hints. + * @param colorHints A combination of color hints. * @hide * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT * @see WallpaperColors#fromBitmap(Bitmap) * @see WallpaperColors#fromDrawable(Drawable) */ - public WallpaperColors(@NonNull Map<Integer, Integer> populationByColor, int colorHints) { + public WallpaperColors(@NonNull Map<Integer, Integer> populationByColor, + @ColorsHints int colorHints) { mAllColors = populationByColor; ArrayList<Map.Entry<Integer, Integer>> mapEntries = new ArrayList( @@ -386,27 +389,14 @@ public final class WallpaperColors implements Parcelable { } /** - * Combination of WallpaperColor hints. - * - * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT - * @return True if dark text is supported. - * @hide + * Returns the color hints for this instance. + * @return The color hints. */ - @SystemApi - public int getColorHints() { + public @ColorsHints int getColorHints() { return mColorHints; } /** - * @param colorHints Combination of WallpaperColors hints. - * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT - * @hide - */ - public void setColorHints(int colorHints) { - mColorHints = colorHints; - } - - /** * Checks if image is bright and clean enough to support light text. * * @param source What to read. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index bb1ff6051d56..0635bd08e22b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2997,6 +2997,7 @@ public class DevicePolicyManager { */ // TODO(b/173541467): should it throw SecurityException if caller is not admin? public boolean isSafeOperation(@OperationSafetyReason int reason) { + throwIfParentInstance("isSafeOperation"); if (mService == null) return false; try { @@ -12239,8 +12240,9 @@ public class DevicePolicyManager { * * @hide */ - public Set<String> getDisallowedSystemApps(ComponentName admin, int userId, - String provisioningAction) { + @TestApi + public @NonNull Set<String> getDisallowedSystemApps(@NonNull ComponentName admin, + @UserIdInt int userId, @NonNull String provisioningAction) { try { return new ArraySet<>( mService.getDisallowedSystemApps(admin, userId, provisioningAction)); @@ -13004,6 +13006,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public @NonNull Set<String> getDefaultCrossProfilePackages() { throwIfParentInstance("getDefaultCrossProfilePackages"); if (mService != null) { diff --git a/core/java/android/app/assist/ActivityId.java b/core/java/android/app/assist/ActivityId.java new file mode 100644 index 000000000000..fb0d056c2a34 --- /dev/null +++ b/core/java/android/app/assist/ActivityId.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.assist; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.IBinder; +import android.os.Parcel; +import android.service.contentcapture.ContentCaptureService; +import android.view.contentcapture.ContentCaptureContext; +import android.view.translation.UiTranslationManager; + +import com.android.internal.annotations.Immutable; + +/** + * The class is used to identify an instance of an Activity. The system provides this to services + * that need to request operations on a specific Activity. For example, the system provides this in + * {@link ContentCaptureContext} to {@link ContentCaptureService} which can use it to issue requests + * like {@link UiTranslationManager#startTranslation}. + * + * @hide + */ +@Immutable +@SystemApi +public class ActivityId { + + /** + * The identifier of the task this activity is in. + */ + private final int mTaskId; + /** + * The identifier of the activity. + */ + @Nullable + private final IBinder mActivityId; + + /** + * @hide + */ + public ActivityId(int taskId, @Nullable IBinder activityId) { + mTaskId = taskId; + mActivityId = activityId; + } + + /** + * @hide + */ + public ActivityId(@NonNull Parcel source) { + mTaskId = source.readInt(); + mActivityId = source.readStrongBinder(); + } + + /** + * The identifier of the task this activity is in. + * @hide + */ + @TestApi + public int getTaskId() { + return mTaskId; + } + + /** + * The identifier of the activity. In some case, this value may be null, e.g. the child session + * of content capture. + * @hide + */ + @Nullable + @TestApi + public IBinder getToken() { + return mActivityId; + } + + /** + * @hide + */ + public void writeToParcel(@NonNull Parcel dest, int parcelableFlags) { + dest.writeInt(mTaskId); + dest.writeStrongBinder(mActivityId); + } + + @Override + public String toString() { + return "ActivityId { taskId = " + mTaskId + ", activityId = " + mActivityId + " }"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ActivityId that = (ActivityId) o; + if (mTaskId != that.mTaskId) { + return false; + } + return mActivityId != null + ? mActivityId.equals(that.mActivityId) + : that.mActivityId == null; + } + + @Override + public int hashCode() { + int result = mTaskId; + result = 31 * result + (mActivityId != null ? mActivityId.hashCode() : 0); + return result; + } +} diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 22492ccd0373..94a4fde0131e 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -403,7 +403,7 @@ public abstract class BackupAgent extends ContextWrapper { public void onFullBackup(FullBackupDataOutput data) throws IOException { FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this, mOperationType); - if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) { + if (!backupScheme.isFullBackupEnabled(data.getTransportFlags())) { return; } @@ -911,7 +911,7 @@ public abstract class BackupAgent extends ContextWrapper { } FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType); - if (!bs.isFullBackupContentEnabled()) { + if (!bs.isFullRestoreEnabled()) { if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { Log.v(FullBackup.TAG_XML_PARSER, "onRestoreFile \"" + destination.getCanonicalPath() diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 829b6cd43934..9b543b571a44 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -99,6 +99,8 @@ public class FullBackup { public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer"; public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION = "fakeClientSideEncryption"; + private static final String FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES + = "disableIfNoEncryptionCapabilities"; /** * When this change is enabled, include / exclude rules specified via @@ -307,6 +309,10 @@ public class FullBackup { // lazy initialized, only when needed private StorageVolume[] mVolumes = null; + // Properties the transport must have (e.g. encryption) for the operation to go ahead. + @Nullable private Integer mRequiredTransportFlags; + @Nullable private Boolean mIsUsingNewScheme; + /** * Parse out the semantic domains into the correct physical location. */ @@ -453,6 +459,35 @@ public class FullBackup { } } + boolean isFullBackupEnabled(int transportFlags) { + try { + if (isUsingNewScheme()) { + int requiredTransportFlags = getRequiredTransportFlags(); + // All bits that are set in requiredTransportFlags must be set in + // transportFlags. + return (transportFlags & requiredTransportFlags) == requiredTransportFlags; + } + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "Failed to interpret the backup scheme: " + e); + return false; + } + + return isFullBackupContentEnabled(); + } + + boolean isFullRestoreEnabled() { + try { + if (isUsingNewScheme()) { + return true; + } + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "Failed to interpret the backup scheme: " + e); + return false; + } + + return isFullBackupContentEnabled(); + } + boolean isFullBackupContentEnabled() { if (mFullBackupContent < 0) { // android:fullBackupContent="false", bail. @@ -491,10 +526,30 @@ public class FullBackup { return mExcludes; } + private synchronized int getRequiredTransportFlags() + throws IOException, XmlPullParserException { + if (mRequiredTransportFlags == null) { + maybeParseBackupSchemeLocked(); + } + + return mRequiredTransportFlags; + } + + private synchronized boolean isUsingNewScheme() + throws IOException, XmlPullParserException { + if (mIsUsingNewScheme == null) { + maybeParseBackupSchemeLocked(); + } + + return mIsUsingNewScheme; + } + private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException { // This not being null is how we know that we've tried to parse the xml already. mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>(); mExcludes = new ArraySet<PathWithRequiredFlags>(); + mRequiredTransportFlags = 0; + mIsUsingNewScheme = false; if (mFullBackupContent == 0 && mDataExtractionRules == 0) { // No scheme specified via either new or legacy config, will copy everything. @@ -535,12 +590,14 @@ public class FullBackup { } if (!mExcludes.isEmpty() || !mIncludes.isEmpty()) { // Found configuration in the new config, we will use it. + mIsUsingNewScheme = true; return; } } if (operationType == OperationType.MIGRATION && CompatChanges.isChangeEnabled(IGNORE_FULL_BACKUP_CONTENT_IN_D2D)) { + mIsUsingNewScheme = true; return; } @@ -584,13 +641,24 @@ public class FullBackup { continue; } - // TODO(b/180523028): Parse required attributes for rules (e.g. encryption). + parseRequiredTransportFlags(parser, configSection); parseRules(parser, excludes, includes, Optional.of(0), configSection); } logParsingResults(excludes, includes); } + private void parseRequiredTransportFlags(XmlPullParser parser, + @ConfigSection String configSection) { + if (ConfigSection.CLOUD_BACKUP.equals(configSection)) { + String encryptionAttribute = parser.getAttributeValue(/* namespace */ null, + FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES); + if ("true".equals(encryptionAttribute)) { + mRequiredTransportFlags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; + } + } + } + @VisibleForTesting public void parseBackupSchemeFromXmlLocked(XmlPullParser parser, Set<PathWithRequiredFlags> excludes, diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index 73a9ceccecee..94ab0dd00113 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -71,6 +71,7 @@ public class LaunchActivityItem extends ClientTransactionItem { private boolean mIsForward; private ProfilerInfo mProfilerInfo; private IBinder mAssistToken; + private IBinder mShareableActivityToken; /** * It is only non-null if the process is the first time to launch activity. It is only an * optimization for quick look up of the interface so the field is ignored for comparison. @@ -95,7 +96,7 @@ public class LaunchActivityItem extends ClientTransactionItem { ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo, - client, mAssistToken, mFixedRotationAdjustments); + client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken); client.handleLaunchActivity(r, pendingActions, null /* customIntent */); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -119,7 +120,7 @@ public class LaunchActivityItem extends ClientTransactionItem { List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken, IActivityClientController activityClientController, - FixedRotationAdjustments fixedRotationAdjustments) { + FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken) { LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class); if (instance == null) { instance = new LaunchActivityItem(); @@ -127,7 +128,7 @@ public class LaunchActivityItem extends ClientTransactionItem { setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer, voiceInteractor, procState, state, persistentState, pendingResults, pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken, - activityClientController, fixedRotationAdjustments); + activityClientController, fixedRotationAdjustments, shareableActivityToken); return instance; } @@ -135,7 +136,7 @@ public class LaunchActivityItem extends ClientTransactionItem { @Override public void recycle() { setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null, - null, false, null, null, null, null); + null, false, null, null, null, null, null); ObjectPool.recycle(this); } @@ -164,6 +165,7 @@ public class LaunchActivityItem extends ClientTransactionItem { dest.writeStrongBinder(mAssistToken); dest.writeStrongInterface(mActivityClientController); dest.writeTypedObject(mFixedRotationAdjustments, flags); + dest.writeStrongBinder(mShareableActivityToken); } /** Read from Parcel. */ @@ -181,7 +183,7 @@ public class LaunchActivityItem extends ClientTransactionItem { in.readTypedObject(ProfilerInfo.CREATOR), in.readStrongBinder(), IActivityClientController.Stub.asInterface(in.readStrongBinder()), - in.readTypedObject(FixedRotationAdjustments.CREATOR)); + in.readTypedObject(FixedRotationAdjustments.CREATOR), in.readStrongBinder()); } public static final @NonNull Creator<LaunchActivityItem> CREATOR = @@ -219,7 +221,8 @@ public class LaunchActivityItem extends ClientTransactionItem { && mIsForward == other.mIsForward && Objects.equals(mProfilerInfo, other.mProfilerInfo) && Objects.equals(mAssistToken, other.mAssistToken) - && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments); + && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments) + && Objects.equals(mShareableActivityToken, other.mShareableActivityToken); } @Override @@ -241,6 +244,7 @@ public class LaunchActivityItem extends ClientTransactionItem { result = 31 * result + Objects.hashCode(mProfilerInfo); result = 31 * result + Objects.hashCode(mAssistToken); result = 31 * result + Objects.hashCode(mFixedRotationAdjustments); + result = 31 * result + Objects.hashCode(mShareableActivityToken); return result; } @@ -277,7 +281,8 @@ public class LaunchActivityItem extends ClientTransactionItem { + ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults + ",pendingNewIntents=" + mPendingNewIntents + ",options=" + mActivityOptions + ",profilerInfo=" + mProfilerInfo + ",assistToken=" + mAssistToken - + ",rotationAdj=" + mFixedRotationAdjustments + "}"; + + ",rotationAdj=" + mFixedRotationAdjustments + + ",shareableActivityToken=" + mShareableActivityToken + "}"; } // Using the same method to set and clear values to make sure we don't forget anything @@ -288,7 +293,7 @@ public class LaunchActivityItem extends ClientTransactionItem { List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken, IActivityClientController activityClientController, - FixedRotationAdjustments fixedRotationAdjustments) { + FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken) { instance.mIntent = intent; instance.mIdent = ident; instance.mInfo = info; @@ -308,5 +313,6 @@ public class LaunchActivityItem extends ClientTransactionItem { instance.mAssistToken = assistToken; instance.mActivityClientController = activityClientController; instance.mFixedRotationAdjustments = fixedRotationAdjustments; + instance.mShareableActivityToken = shareableActivityToken; } } diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 1d5dc1d5df1c..098d8b6c6058 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -16,6 +16,8 @@ package android.app.usage; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -28,8 +30,11 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.DataUsageRequest; import android.net.INetworkStatsService; +import android.net.Network; import android.net.NetworkStack; +import android.net.NetworkStateSnapshot; import android.net.NetworkTemplate; +import android.net.UnderlyingNetworkInfo; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.net.netstats.provider.NetworkStatsProvider; import android.os.Binder; @@ -48,6 +53,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetworkIdentityUtils; +import java.util.List; import java.util.Objects; /** @@ -633,6 +639,50 @@ public class NetworkStatsManager { return template; } + /** + * Notify {@code NetworkStatsService} about network status changed. + * + * Notifies NetworkStatsService of network state changes for data usage accounting purposes. + * + * To avoid races that attribute data usage to wrong network, such as new network with + * the same interface after SIM hot-swap, this function will not return until + * {@code NetworkStatsService} finishes its work of retrieving traffic statistics from + * all data sources. + * + * @param defaultNetworks the list of all networks that could be used by network traffic that + * does not explicitly select a network. + * @param networkStateSnapshots a list of {@link NetworkStateSnapshot}s, one for + * each network that is currently connected. + * @param activeIface the active (i.e., connected) default network interface for the calling + * uid. Used to determine on which network future calls to + * {@link android.net.TrafficStats#incrementOperationCount} applies to. + * @param underlyingNetworkInfos the list of underlying network information for all + * currently-connected VPNs. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void notifyNetworkStatus( + @NonNull List<Network> defaultNetworks, + @NonNull List<NetworkStateSnapshot> networkStateSnapshots, + @Nullable String activeIface, + @NonNull List<UnderlyingNetworkInfo> underlyingNetworkInfos) { + try { + Objects.requireNonNull(defaultNetworks); + Objects.requireNonNull(networkStateSnapshots); + Objects.requireNonNull(underlyingNetworkInfos); + // TODO: Change internal namings after the name is decided. + mService.forceUpdateIfaces(defaultNetworks.toArray(new Network[0]), + networkStateSnapshots.toArray(new NetworkStateSnapshot[0]), activeIface, + underlyingNetworkInfos.toArray(new UnderlyingNetworkInfo[0])); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private static class CallbackHandler extends Handler { private final int mNetworkType; private final String mSubscriberId; diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 081f4fdc1b12..e6a4656bdbc5 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -334,10 +334,19 @@ public final class UsageEvents implements Parcelable { public static final int LOCUS_ID_SET = 30; /** + * An event type denoting that a component in the package has been used (e.g. broadcast + * receiver, service, content provider). This generally matches up with usage that would + * cause an app to leave force stop. The component itself is not provided as we are only + * interested in whether the package is used, not the component itself. + * @hide + */ + public static final int APP_COMPONENT_USED = 31; + + /** * Keep in sync with the greatest event type value. * @hide */ - public static final int MAX_EVENT_TYPE = 30; + public static final int MAX_EVENT_TYPE = 31; /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index fc54c716d4ec..d79fac58cf12 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -39,6 +39,7 @@ import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; +import android.util.SizeF; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.Gravity; @@ -56,7 +57,6 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.Executor; /** @@ -93,7 +93,7 @@ public class AppWidgetHostView extends FrameLayout { int mLayoutId = -1; private InteractionHandler mInteractionHandler; private boolean mOnLightBackground; - private PointF mCurrentSize = null; + private SizeF mCurrentSize = null; private RemoteViews.ColorResources mColorResources = null; // Stores the last remote views last inflated. private RemoteViews mLastInflatedRemoteViews = null; @@ -253,9 +253,33 @@ public class AppWidgetHostView extends FrameLayout { } } + private SizeF computeSizeFromLayout(int left, int top, int right, int bottom) { + Rect padding = getDefaultPadding(); + float density = getResources().getDisplayMetrics().density; + return new SizeF( + (right - left - padding.right - padding.left) / density, + (bottom - top - padding.bottom - padding.top) / density + ); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { try { + SizeF oldSize = mCurrentSize; + SizeF newSize = computeSizeFromLayout(left, top, right, bottom); + mCurrentSize = newSize; + if (mLastInflatedRemoteViews != null) { + RemoteViews toApply = mLastInflatedRemoteViews.getRemoteViewsToApplyIfDifferent( + oldSize, newSize); + if (toApply != null) { + applyRemoteViews(toApply, false); + measureChildWithMargins(mView, + MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), + 0 /* widthUsed */, + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY), + 0 /* heightUsed */); + } + } super.onLayout(changed, left, top, right, bottom); } catch (final RuntimeException e) { Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e); @@ -309,7 +333,7 @@ public class AppWidgetHostView extends FrameLayout { * again. Typically, this will be size of the widget in landscape and portrait. * On some foldables, this might include the size on the outer and inner screens. */ - public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<PointF> sizes) { + public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<SizeF> sizes) { AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext); Rect padding = getDefaultPadding(); @@ -318,20 +342,20 @@ public class AppWidgetHostView extends FrameLayout { float xPaddingDips = (padding.left + padding.right) / density; float yPaddingDips = (padding.top + padding.bottom) / density; - ArrayList<PointF> paddedSizes = new ArrayList<>(sizes.size()); + ArrayList<SizeF> paddedSizes = new ArrayList<>(sizes.size()); float minWidth = Float.MAX_VALUE; float maxWidth = 0; float minHeight = Float.MAX_VALUE; float maxHeight = 0; for (int i = 0; i < sizes.size(); i++) { - PointF size = sizes.get(i); - PointF paddedPoint = new PointF(Math.max(0.f, size.x - xPaddingDips), - Math.max(0.f, size.y - yPaddingDips)); - paddedSizes.add(paddedPoint); - minWidth = Math.min(minWidth, paddedPoint.x); - maxWidth = Math.max(maxWidth, paddedPoint.x); - minHeight = Math.min(minHeight, paddedPoint.y); - maxHeight = Math.max(maxHeight, paddedPoint.y); + SizeF size = sizes.get(i); + SizeF paddedSize = new SizeF(Math.max(0.f, size.getWidth() - xPaddingDips), + Math.max(0.f, size.getHeight() - yPaddingDips)); + paddedSizes.add(paddedSize); + minWidth = Math.min(minWidth, paddedSize.getWidth()); + maxWidth = Math.max(maxWidth, paddedSize.getWidth()); + minHeight = Math.min(minHeight, paddedSize.getHeight()); + maxHeight = Math.max(maxHeight, paddedSize.getHeight()); } if (paddedSizes.equals( widgetManager.getAppWidgetOptions(mAppWidgetId).<PointF>getParcelableArrayList( @@ -348,35 +372,6 @@ public class AppWidgetHostView extends FrameLayout { } /** - * Set the current size of the widget. This should be the full area the AppWidgetHostView is - * given. Padding added by the framework will be accounted for automatically. - * - * This size will be used to choose the appropriate layout the next time the {@link RemoteViews} - * is re-inflated, if it was created with {@link RemoteViews#RemoteViews(Map)} . - */ - public void setCurrentSize(@NonNull PointF size) { - Rect padding = getDefaultPadding(); - float density = getResources().getDisplayMetrics().density; - float xPaddingDips = (padding.left + padding.right) / density; - float yPaddingDips = (padding.top + padding.bottom) / density; - PointF newSize = new PointF(size.x - xPaddingDips, size.y - yPaddingDips); - if (!newSize.equals(mCurrentSize)) { - mCurrentSize = newSize; - reapplyLastRemoteViews(); - } - } - - /** - * Clear the current size, indicating it is not currently known. - */ - public void clearCurrentSize() { - if (mCurrentSize != null) { - mCurrentSize = null; - reapplyLastRemoteViews(); - } - } - - /** * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -514,23 +509,22 @@ public class AppWidgetHostView extends FrameLayout { mLayoutId = -1; mViewMode = VIEW_MODE_DEFAULT; } else { + // Select the remote view we are actually going to apply. + RemoteViews rvToApply = remoteViews.getRemoteViewsToApply(mContext, mCurrentSize); if (mOnLightBackground) { - remoteViews = remoteViews.getDarkTextViews(); + rvToApply = rvToApply.getDarkTextViews(); } if (mAsyncExecutor != null && useAsyncIfPossible) { - inflateAsync(remoteViews); + inflateAsync(rvToApply); return; } - // Prepare a local reference to the remote Context so we're ready to - // inflate any requested LayoutParams. - mRemoteContext = getRemoteContext(); - int layoutId = remoteViews.getLayoutId(); + int layoutId = rvToApply.getLayoutId(); // If our stale view has been prepared to match active, and the new // layout matches, try recycling it if (content == null && layoutId == mLayoutId) { try { - remoteViews.reapply(mContext, mView, mInteractionHandler, mCurrentSize, + rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize, mColorResources); content = mView; recycled = true; @@ -543,7 +537,7 @@ public class AppWidgetHostView extends FrameLayout { // Try normal RemoteView inflation if (content == null) { try { - content = remoteViews.apply(mContext, this, mInteractionHandler, + content = rvToApply.apply(mContext, this, mInteractionHandler, mCurrentSize, mColorResources); if (LOGD) Log.d(TAG, "had to inflate new layout"); } catch (RuntimeException e) { diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 38919f61d9df..a88aed7f20a3 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -219,7 +219,7 @@ public class AppWidgetManager { public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight"; /** - * A bundle extra ({@code List<PointF>}) that contains the list of possible sizes, in dips, a + * A bundle extra ({@code List<SizeF>}) that contains the list of possible sizes, in dips, a * widget instance can take. */ public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes"; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index e7661dbad749..ec94faa544d3 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -374,6 +374,35 @@ public final class BluetoothDevice implements Parcelable { public static final String ACTION_SDP_RECORD = "android.bluetooth.device.action.SDP_RECORD"; + /** @hide */ + @IntDef(prefix = "METADATA_", value = { + METADATA_MANUFACTURER_NAME, + METADATA_MODEL_NAME, + METADATA_SOFTWARE_VERSION, + METADATA_HARDWARE_VERSION, + METADATA_COMPANION_APP, + METADATA_MAIN_ICON, + METADATA_IS_UNTETHERED_HEADSET, + METADATA_UNTETHERED_LEFT_ICON, + METADATA_UNTETHERED_RIGHT_ICON, + METADATA_UNTETHERED_CASE_ICON, + METADATA_UNTETHERED_LEFT_BATTERY, + METADATA_UNTETHERED_RIGHT_BATTERY, + METADATA_UNTETHERED_CASE_BATTERY, + METADATA_UNTETHERED_LEFT_CHARGING, + METADATA_UNTETHERED_RIGHT_CHARGING, + METADATA_UNTETHERED_CASE_CHARGING, + METADATA_ENHANCED_SETTINGS_UI_URI, + METADATA_DEVICE_TYPE, + METADATA_MAIN_BATTERY, + METADATA_MAIN_CHARGING, + METADATA_MAIN_LOW_BATTERY_THRESHOLD, + METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD, + METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD, + METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD}) + @Retention(RetentionPolicy.SOURCE) + public @interface MetadataKey{} + /** * Maximum length of a metadata entry, this is to avoid exploding Bluetooth * disk usage @@ -523,6 +552,89 @@ public final class BluetoothDevice implements Parcelable { public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; /** + * Type of the Bluetooth device, must be within the list of + * BluetoothDevice.DEVICE_TYPE_* + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_DEVICE_TYPE = 17; + + /** + * Battery level of the Bluetooth device, use when the Bluetooth device + * does not support HFP battery indicator. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_BATTERY = 18; + + /** + * Whether the device is charging. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_CHARGING = 19; + + /** + * The battery threshold of the Bluetooth device to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; + + /** + * The battery threshold of the left headset to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; + + /** + * The battery threshold of the right headset to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; + + /** + * The battery threshold of the case to show low battery icon. + * Data type should be {@String} as {@link Byte} array. + * @hide + */ + @SystemApi + public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; + + /** + * Device type which is used in METADATA_DEVICE_TYPE + * Indicates this Bluetooth device is a standard Bluetooth accessory or + * not listed in METADATA_DEVICE_TYPE_*. + * @hide + */ + @SystemApi + public static final String DEVICE_TYPE_DEFAULT = "Default"; + + /** + * Device type which is used in METADATA_DEVICE_TYPE + * Indicates this Bluetooth device is a watch. + * @hide + */ + @SystemApi + public static final String DEVICE_TYPE_WATCH = "Watch"; + + /** + * Device type which is used in METADATA_DEVICE_TYPE + * Indicates this Bluetooth device is an untethered headset. + * @hide + */ + @SystemApi + public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset"; + + /** * Broadcast Action: This intent is used to broadcast the {@link UUID} * wrapped as a {@link android.os.ParcelUuid} of the remote device after it * has been fetched. This intent is sent only when the UUIDs of the remote @@ -2316,7 +2428,7 @@ public final class BluetoothDevice implements Parcelable { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean setMetadata(int key, @NonNull byte[] value) { + public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) { final IBluetooth service = sService; if (service == null) { Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); @@ -2344,7 +2456,7 @@ public final class BluetoothDevice implements Parcelable { @SystemApi @Nullable @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public byte[] getMetadata(int key) { + public byte[] getMetadata(@MetadataKey int key) { final IBluetooth service = sService; if (service == null) { Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); @@ -2357,4 +2469,14 @@ public final class BluetoothDevice implements Parcelable { return null; } } + + /** + * Get the maxinum metadata key ID. + * + * @return the last supported metadata key + * @hide + */ + public static @MetadataKey int getMaxMetadataKey() { + return METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD; + } } diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java index e3a130c4b436..4e64dbed7017 100644 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java +++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java @@ -22,7 +22,7 @@ import android.os.Parcelable; /** * The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic * advertising preferences for each Bluetooth LE advertising set. Use {@link - * AdvertisingSetParameters.Builder} to create an instance of this class. + * PeriodicAdvertisingParameters.Builder} to create an instance of this class. */ public final class PeriodicAdvertisingParameters implements Parcelable { diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 102c98ff9329..b07d6d5b1bc3 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -60,10 +60,16 @@ public final class AssociationRequest implements Parcelable { /** * Device profile: watch. * + * If specified, the current request may have a modified UI to highlight that the device being + * set up is a specific kind of device, and some extra permissions may be granted to the app + * as a result. + * + * Using it requires declaring uses-permission + * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH} in the manifest. + * * @see AssociationRequest.Builder#setDeviceProfile */ - public static final String DEVICE_PROFILE_WATCH = - "android.app.role.COMPANION_DEVICE_WATCH"; + public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH"; /** @hide */ @StringDef(value = { DEVICE_PROFILE_WATCH }) @@ -107,6 +113,17 @@ public final class AssociationRequest implements Parcelable { */ private @Nullable String mDeviceProfilePrivilegesDescription = null; + /** + * The time at which his request was created + * + * @hide + */ + private long mCreationTime; + + private void onConstructed() { + mCreationTime = System.currentTimeMillis(); + } + /** @hide */ public void setCallingPackage(@NonNull String pkg) { mCallingPackage = pkg; @@ -187,7 +204,7 @@ public final class AssociationRequest implements Parcelable { markUsed(); return new AssociationRequest( mSingleDevice, emptyIfNull(mDeviceFilters), - mDeviceProfile, null, null); + mDeviceProfile, null, null, -1L); } } @@ -228,6 +245,8 @@ public final class AssociationRequest implements Parcelable { * The user-readable description of the device profile's privileges. * * Populated by the system. + * @param creationTime + * The time at which his request was created * @hide */ @DataClass.Generated.Member @@ -236,7 +255,8 @@ public final class AssociationRequest implements Parcelable { @NonNull List<DeviceFilter<?>> deviceFilters, @Nullable @DeviceProfile String deviceProfile, @Nullable String callingPackage, - @Nullable String deviceProfilePrivilegesDescription) { + @Nullable String deviceProfilePrivilegesDescription, + long creationTime) { this.mSingleDevice = singleDevice; this.mDeviceFilters = deviceFilters; com.android.internal.util.AnnotationValidations.validate( @@ -246,8 +266,9 @@ public final class AssociationRequest implements Parcelable { DeviceProfile.class, null, mDeviceProfile); this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; + this.mCreationTime = creationTime; - // onConstructed(); // You can define this method to get a callback + onConstructed(); } /** @@ -284,6 +305,16 @@ public final class AssociationRequest implements Parcelable { return mDeviceProfilePrivilegesDescription; } + /** + * The time at which his request was created + * + * @hide + */ + @DataClass.Generated.Member + public long getCreationTime() { + return mCreationTime; + } + @Override @DataClass.Generated.Member public String toString() { @@ -295,7 +326,8 @@ public final class AssociationRequest implements Parcelable { "deviceFilters = " + mDeviceFilters + ", " + "deviceProfile = " + mDeviceProfile + ", " + "callingPackage = " + mCallingPackage + ", " + - "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + + "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " + + "creationTime = " + mCreationTime + " }"; } @@ -316,7 +348,8 @@ public final class AssociationRequest implements Parcelable { && Objects.equals(mDeviceFilters, that.mDeviceFilters) && Objects.equals(mDeviceProfile, that.mDeviceProfile) && Objects.equals(mCallingPackage, that.mCallingPackage) - && Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription); + && Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription) + && mCreationTime == that.mCreationTime; } @Override @@ -331,6 +364,7 @@ public final class AssociationRequest implements Parcelable { _hash = 31 * _hash + Objects.hashCode(mDeviceProfile); _hash = 31 * _hash + Objects.hashCode(mCallingPackage); _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription); + _hash = 31 * _hash + Long.hashCode(mCreationTime); return _hash; } @@ -350,6 +384,7 @@ public final class AssociationRequest implements Parcelable { if (mDeviceProfile != null) dest.writeString(mDeviceProfile); if (mCallingPackage != null) dest.writeString(mCallingPackage); if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription); + dest.writeLong(mCreationTime); } @Override @@ -370,6 +405,7 @@ public final class AssociationRequest implements Parcelable { String deviceProfile = (flg & 0x4) == 0 ? null : in.readString(); String callingPackage = (flg & 0x8) == 0 ? null : in.readString(); String deviceProfilePrivilegesDescription = (flg & 0x10) == 0 ? null : in.readString(); + long creationTime = in.readLong(); this.mSingleDevice = singleDevice; this.mDeviceFilters = deviceFilters; @@ -380,8 +416,9 @@ public final class AssociationRequest implements Parcelable { DeviceProfile.class, null, mDeviceProfile); this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; + this.mCreationTime = creationTime; - // onConstructed(); // You can define this method to get a callback + onConstructed(); } @DataClass.Generated.Member @@ -399,10 +436,10 @@ public final class AssociationRequest implements Parcelable { }; @DataClass.Generated( - time = 1611692924843L, + time = 1614976943652L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java", - inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)") + inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 59646f106db5..b441b364cec8 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -16,6 +16,7 @@ package android.companion; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -141,6 +142,10 @@ public final class CompanionDeviceManager { * <p>Calling this API requires a uses-feature * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> * + * <p>When using {@link AssociationRequest#DEVICE_PROFILE_WATCH watch} + * {@link AssociationRequest.Builder#setDeviceProfile profile}, caller must also hold + * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH}</p> + * * @param request specific details about this request * @param callback will be called once there's at least one device found for user to choose from * @param handler A handler to control which thread the callback will be delivered on, or null, @@ -148,6 +153,9 @@ public final class CompanionDeviceManager { * * @see AssociationRequest */ + @RequiresPermission( + value = Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, + conditional = true) public void associate( @NonNull AssociationRequest request, @NonNull Callback callback, diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index f3a4e1f79955..48754cccfe0d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -370,6 +370,15 @@ public abstract class Context { /*********** Hidden flags below this line ***********/ /** + * Flag for {@link #bindService}: This flag is only intended to be used by the system to + * indicate that a service binding is not considered as real package component usage and should + * not generate a {@link android.app.usage.UsageEvents.Event#APP_COMPONENT_USED} event in usage + * stats. + * @hide + */ + public static final int BIND_NOT_APP_COMPONENT_USAGE = 0x00008000; + + /** * Flag for {@link #bindService}: allow the process hosting the target service to be treated * as if it's as important as a perceptible app to the user and avoid the oom killer killing * this process in low memory situations until there aren't any other processes left but the @@ -6368,6 +6377,19 @@ public abstract class Context { } /** + * Creates a context with specific properties and behaviors. + * + * @param contextParams Parameters for how the new context should behave. + * @return A context with the specified behaviors. + * + * @see ContextParams + */ + @NonNull + public Context createContext(@NonNull ContextParams contextParams) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Return a new Context object for the current Context but attribute to a different tag. * In complex apps attribution tagging can be used to distinguish between separate logical * parts. diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java new file mode 100644 index 000000000000..16128a650c12 --- /dev/null +++ b/core/java/android/content/ContextParams.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 android.content; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * This class represents rules around how a context being created via + * {@link Context#createContext} should behave. + * + * <p>One of the dimensions to customize is how permissions should behave. + * For example, you can specify how permission accesses from a context should + * be attributed in the platform's permission tracking system. + * + * <p>The two main types of attribution are: against an attribution tag which + * is an arbitrary string your app specifies for the purposes of tracking permission + * accesses from a given portion of your app; against another package and optionally + * its attribution tag if you are accessing the data on behalf of another app and + * you will be passing that data to this app. Both attributions are not mutually + * exclusive. + * + * <p>For example if you have a feature "foo" in your app which accesses + * permissions on behalf of app "foo.bar.baz" with feature "bar" you need to + * create a context like this: + * + * <pre class="prettyprint"> + * context.createContext(new ContextParams.Builder() + * .setAttributionTag("foo") + * .setReceiverPackage("foo.bar.baz", "bar") + * .build()) + * </pre> + * + * @see Context#createContext(ContextParams) + */ +public final class ContextParams { + + private ContextParams() { + /* hide ctor */ + } + + /** + * @return The attribution tag. + */ + @Nullable + public String getAttributionTag() { + return null; + } + + /** + * @return The receiving package. + */ + @Nullable + public String getReceiverPackage() { + return null; + } + + /** + * @return The receiving package's attribution tag. + */ + @Nullable + public String getReceiverAttributionTag() { + return null; + } + + /** + * Builder for creating a {@link ContextParams}. + */ + public static final class Builder { + + /** + * Sets an attribution tag against which to track permission accesses. + * + * @param attributionTag The attribution tag. + * @return This builder. + */ + @NonNull + public Builder setAttributionTag(@NonNull String attributionTag) { + return this; + } + + /** + * Sets the package and its optional attribution tag that would be receiving + * the permission protected data. + * + * @param packageName The package name receiving the permission protected data. + * @param attributionTag An attribution tag of the receiving package. + * @return This builder. + */ + @NonNull + public Builder setReceiverPackage(@NonNull String packageName, + @Nullable String attributionTag) { + return this; + } + + /** + * Creates a new instance. You need to either specify an attribution tag + * or a receiver package or both. + * + * @return The new instance. + */ + @NonNull + public ContextParams build() { + return new ContextParams(); + } + } +} diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS index 144856b68e7f..d0d406a0c9e6 100644 --- a/core/java/android/content/OWNERS +++ b/core/java/android/content/OWNERS @@ -4,4 +4,7 @@ per-file ContextWrapper.java = * per-file IntentFilter.java = toddke@google.com per-file IntentFilter.java = patb@google.com per-file Intent.java = toddke@google.com -per-file Intent.java = patb@google.com
\ No newline at end of file +per-file Intent.java = patb@google.com +per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS +per-file ContentCaptureOptions* = file:/core/java/android/service/contentcapture/OWNERS +per-file LocusId* = file:/core/java/android/service/contentcapture/OWNERS diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 0aa1be94d279..1a5dad5f7596 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1205,7 +1205,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { CATEGORY_SOCIAL, CATEGORY_NEWS, CATEGORY_MAPS, - CATEGORY_PRODUCTIVITY + CATEGORY_PRODUCTIVITY, + CATEGORY_ACCESSIBILITY }) @Retention(RetentionPolicy.SOURCE) public @interface Category { @@ -1281,6 +1282,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int CATEGORY_PRODUCTIVITY = 7; /** + * Category for apps which are primarily accessibility apps, such as screen-readers. + * + * @see #category + */ + public static final int CATEGORY_ACCESSIBILITY = 8; + + /** * Return a concise, localized title for the given * {@link ApplicationInfo#category} value, or {@code null} for unknown * values such as {@link #CATEGORY_UNDEFINED}. @@ -1305,6 +1313,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return context.getText(com.android.internal.R.string.app_category_maps); case ApplicationInfo.CATEGORY_PRODUCTIVITY: return context.getText(com.android.internal.R.string.app_category_productivity); + case ApplicationInfo.CATEGORY_ACCESSIBILITY: + return context.getText(com.android.internal.R.string.app_category_accessibility); default: return null; } diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index b34574811bca..c91334a5470f 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -21,32 +21,34 @@ import android.content.IntentSender; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; -/** - * {@hide} - */ +import com.android.internal.infra.AndroidFuture; + +/** {@hide} */ interface IShortcutService { - boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, - int userId); + oneway void setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, + int userId, in AndroidFuture callback); - boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, - int userId); + oneway void addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, + int userId, in AndroidFuture callback); - void removeDynamicShortcuts(String packageName, in List shortcutIds, int userId); + oneway void removeDynamicShortcuts(String packageName, in List shortcutIds, int userId); - void removeAllDynamicShortcuts(String packageName, int userId); + oneway void removeAllDynamicShortcuts(String packageName, int userId); - boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId); + oneway void updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId, + in AndroidFuture callback); - boolean requestPinShortcut(String packageName, in ShortcutInfo shortcut, - in IntentSender resultIntent, int userId); + oneway void requestPinShortcut(String packageName, in ShortcutInfo shortcut, + in IntentSender resultIntent, int userId, in AndroidFuture callback); - Intent createShortcutResultIntent(String packageName, in ShortcutInfo shortcut, int userId); + oneway void createShortcutResultIntent(String packageName, in ShortcutInfo shortcut, + int userId, in AndroidFuture callback); - void disableShortcuts(String packageName, in List shortcutIds, CharSequence disabledMessage, - int disabledMessageResId, int userId); + oneway void disableShortcuts(String packageName, in List shortcutIds, + CharSequence disabledMessage, int disabledMessageResId, int userId); - void enableShortcuts(String packageName, in List shortcutIds, int userId); + oneway void enableShortcuts(String packageName, in List shortcutIds, int userId); int getMaxShortcutCountPerActivity(String packageName, int userId); @@ -56,29 +58,31 @@ interface IShortcutService { int getIconMaxDimensions(String packageName, int userId); - void reportShortcutUsed(String packageName, String shortcutId, int userId); + oneway void reportShortcutUsed(String packageName, String shortcutId, int userId); - void resetThrottling(); // system only API for developer opsions + oneway void resetThrottling(); // system only API for developer opsions - void onApplicationActive(String packageName, int userId); // system only API for sysUI + oneway void onApplicationActive(String packageName, int userId); // system only API for sysUI byte[] getBackupPayload(int user); - void applyRestore(in byte[] payload, int user); + oneway void applyRestore(in byte[] payload, int user); boolean isRequestPinItemSupported(int user, int requestType); // System API used by framework's ShareSheet (ChooserActivity) - ParceledListSlice getShareTargets(String packageName, in IntentFilter filter, int userId); + oneway void getShareTargets(String packageName, in IntentFilter filter, int userId, + in AndroidFuture<ParceledListSlice> callback); boolean hasShareTargets(String packageName, String packageToCheck, int userId); - void removeLongLivedShortcuts(String packageName, in List shortcutIds, int userId); + oneway void removeLongLivedShortcuts(String packageName, in List shortcutIds, int userId); - ParceledListSlice getShortcuts(String packageName, int matchFlags, int userId); + oneway void getShortcuts(String packageName, int matchFlags, int userId, + in AndroidFuture<ParceledListSlice<ShortcutInfo>> callback); - void pushDynamicShortcut(String packageName, in ShortcutInfo shortcut, int userId); + oneway void pushDynamicShortcut(String packageName, in ShortcutInfo shortcut, int userId); - void updateShortcutVisibility(String callingPkg, String packageName, in byte[] certificate, - in boolean visible, int userId); -}
\ No newline at end of file + oneway void updateShortcutVisibility(String callingPkg, String packageName, + in byte[] certificate, in boolean visible, int userId); +} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 29dea6bb09db..d79b66c1cf56 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3584,30 +3584,18 @@ public abstract class PackageManager { * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has * the requisite kernel support to support incremental delivery aka Incremental FileSystem. * - * @see IncrementalManager#isFeatureEnabled - * @hide - * - * @deprecated Use {@link #FEATURE_INCREMENTAL_DELIVERY_VERSION} instead. - */ - @Deprecated - @SystemApi - @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_INCREMENTAL_DELIVERY = - "android.software.incremental_delivery"; - - /** - * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * feature not present - IncFs is not present on the device. * 1 - IncFs v1, core features, no PerUid support. Optional in R. * 2 - IncFs v2, PerUid support, fs-verity support. Required in S. * + * @see IncrementalManager#isFeatureEnabled * @see IncrementalManager#getVersion() * @hide */ @SystemApi @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = - "android.software.incremental_delivery_version"; + public static final String FEATURE_INCREMENTAL_DELIVERY = + "android.software.incremental_delivery"; /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: @@ -3656,17 +3644,6 @@ public abstract class PackageManager { public static final String FEATURE_APP_ENUMERATION = "android.software.app_enumeration"; /** - * Feature for {@link android.view.WindowManager.LayoutParams.backgroundBlurRedius} and - * {@link android.graphics.drawable.BackgroundBlurDrawable}: the device supports cross-layer - * blurring. - * - * @hide - */ - @SystemApi - @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur"; - - /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has * a Keystore implementation that can only enforce limited use key in hardware with max usage * count equals to 1. @@ -7040,7 +7017,7 @@ public abstract class PackageManager { * domain to an application, use * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}, * passing in all of the domains returned inside - * {@link DomainVerificationManager#getDomainVerificationUserSelection(String)}. + * {@link DomainVerificationManager#getDomainVerificationUserState(String)}. * * @hide */ diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 7ecb11248d23..7696cbe0b631 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -42,6 +42,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; @@ -161,18 +162,20 @@ public abstract class RegisteredServicesCache<V> { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); - mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null); + Handler handler = BackgroundThread.getHandler(); + mContext.registerReceiverAsUser( + mPackageReceiver, UserHandle.ALL, intentFilter, null, handler); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mExternalReceiver, sdFilter); + mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler); // Register for user-related events IntentFilter userFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiver(mUserRemovedReceiver, userFilter); + mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler); } private void handlePackageEvent(Intent intent, int userId) { @@ -265,7 +268,7 @@ public abstract class RegisteredServicesCache<V> { public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) { if (handler == null) { - handler = new Handler(mContext.getMainLooper()); + handler = BackgroundThread.getHandler(); } synchronized (this) { mHandler = handler; diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index d3bac79aa2b9..f584ff33fa50 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -24,6 +24,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.annotation.WorkerThread; import android.app.Notification; import android.app.usage.UsageStatsManager; import android.compat.annotation.UnsupportedAppUsage; @@ -42,10 +43,12 @@ import android.os.ServiceManager; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.concurrent.ExecutionException; /** * <p><code>ShortcutManager</code> executes operations on an app's set of <i>shortcuts</i>, which @@ -140,13 +143,16 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ + @WorkerThread public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { + final AndroidFuture<Boolean> future = new AndroidFuture<>(); try { - return mService.setDynamicShortcuts(mContext.getPackageName(), - new ParceledListSlice(shortcutInfoList), injectMyUserId()); + mService.setDynamicShortcuts(mContext.getPackageName(), + new ParceledListSlice(shortcutInfoList), injectMyUserId(), future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future); } /** @@ -158,14 +164,17 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ + @WorkerThread @NonNull public List<ShortcutInfo> getDynamicShortcuts() { + final AndroidFuture<ParceledListSlice<ShortcutInfo>> future = new AndroidFuture<>(); try { - return mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_DYNAMIC, - injectMyUserId()).getList(); + mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_DYNAMIC, injectMyUserId(), + future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future).getList(); } /** @@ -177,14 +186,17 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ + @WorkerThread @NonNull public List<ShortcutInfo> getManifestShortcuts() { + final AndroidFuture<ParceledListSlice<ShortcutInfo>> future = new AndroidFuture<>(); try { - return mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_MANIFEST, - injectMyUserId()).getList(); + mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_MANIFEST, injectMyUserId(), + future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future).getList(); } /** @@ -205,14 +217,16 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ + @WorkerThread @NonNull public List<ShortcutInfo> getShortcuts(@ShortcutMatchFlags int matchFlags) { + final AndroidFuture<ParceledListSlice<ShortcutInfo>> future = new AndroidFuture<>(); try { - return mService.getShortcuts(mContext.getPackageName(), matchFlags, injectMyUserId()) - .getList(); + mService.getShortcuts(mContext.getPackageName(), matchFlags, injectMyUserId(), future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future).getList(); } /** @@ -228,13 +242,16 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ + @WorkerThread public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { + final AndroidFuture<Boolean> future = new AndroidFuture<>(); try { - return mService.addDynamicShortcuts(mContext.getPackageName(), - new ParceledListSlice(shortcutInfoList), injectMyUserId()); + mService.addDynamicShortcuts(mContext.getPackageName(), + new ParceledListSlice(shortcutInfoList), injectMyUserId(), future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future); } /** @@ -287,14 +304,17 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ + @WorkerThread @NonNull public List<ShortcutInfo> getPinnedShortcuts() { + final AndroidFuture<ParceledListSlice<ShortcutInfo>> future = new AndroidFuture<>(); try { - return mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_PINNED, - injectMyUserId()).getList(); + mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_PINNED, injectMyUserId(), + future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future).getList(); } /** @@ -309,13 +329,16 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ + @WorkerThread public boolean updateShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { + final AndroidFuture<Boolean> future = new AndroidFuture<>(); try { - return mService.updateShortcuts(mContext.getPackageName(), - new ParceledListSlice(shortcutInfoList), injectMyUserId()); + mService.updateShortcuts(mContext.getPackageName(), + new ParceledListSlice(shortcutInfoList), injectMyUserId(), future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future); } /** @@ -584,14 +607,17 @@ public class ShortcutManager { * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground * service, or the device is locked. */ + @WorkerThread public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut, @Nullable IntentSender resultIntent) { + final AndroidFuture<Boolean> future = new AndroidFuture<>(); try { - return mService.requestPinShortcut(mContext.getPackageName(), shortcut, - resultIntent, injectMyUserId()); + mService.requestPinShortcut(mContext.getPackageName(), shortcut, + resultIntent, injectMyUserId(), future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future); } /** @@ -611,13 +637,16 @@ public class ShortcutManager { * * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled. */ + @WorkerThread public Intent createShortcutResultIntent(@NonNull ShortcutInfo shortcut) { + final AndroidFuture<Intent> future = new AndroidFuture<>(); try { - return mService.createShortcutResultIntent(mContext.getPackageName(), shortcut, - injectMyUserId()); + mService.createShortcutResultIntent(mContext.getPackageName(), shortcut, + injectMyUserId(), future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future); } /** @@ -650,16 +679,18 @@ public class ShortcutManager { * @return List of {@link ShareShortcutInfo}s that match the given IntentFilter. * @hide */ + @WorkerThread @NonNull @SystemApi @RequiresPermission(Manifest.permission.MANAGE_APP_PREDICTIONS) public List<ShareShortcutInfo> getShareTargets(@NonNull IntentFilter filter) { + final AndroidFuture<ParceledListSlice> future = new AndroidFuture<>(); try { - return mService.getShareTargets(mContext.getPackageName(), filter, - injectMyUserId()).getList(); + mService.getShareTargets(mContext.getPackageName(), filter, injectMyUserId(), future); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + future.completeExceptionally(e); } + return getFutureOrThrow(future).getList(); } /** @@ -788,4 +819,21 @@ public class ShortcutManager { throw e.rethrowFromSystemServer(); } } + + private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) { + try { + return future.get(); + } catch (Throwable e) { + if (e instanceof ExecutionException) { + e = e.getCause(); + } + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + if (e instanceof Error) { + throw (Error) e; + } + throw new RuntimeException(e); + } + } } diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java index 2ea24f71371b..6f478accedd7 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivity.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java @@ -155,6 +155,7 @@ public class ParsedActivity extends ParsedMainComponent { alias.nonLocalizedLabel = target.nonLocalizedLabel; alias.launchMode = target.launchMode; alias.lockTaskLaunchMode = target.lockTaskLaunchMode; + alias.documentLaunchMode = target.documentLaunchMode; alias.descriptionRes = target.descriptionRes; alias.screenOrientation = target.screenOrientation; alias.taskAffinity = target.taskAffinity; @@ -179,7 +180,6 @@ public class ParsedActivity extends ParsedMainComponent { // alias.exported = target.exported; // alias.permission = target.permission; // alias.splitName = target.splitName; -// alias.documentLaunchMode = target.documentLaunchMode; // alias.persistableMode = target.persistableMode; // alias.rotationAnimation = target.rotationAnimation; // alias.colorMode = target.colorMode; diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS index d302b0ae1ea8..cf7e6890876a 100644 --- a/core/java/android/content/pm/permission/OWNERS +++ b/core/java/android/content/pm/permission/OWNERS @@ -1,10 +1,8 @@ # Bug component: 137825 +include platform/frameworks/base:/core/java/android/permission/OWNERS + toddke@android.com toddke@google.com patb@google.com -svetoslavganov@android.com -svetoslavganov@google.com -zhanghai@google.com -evanseverson@google.com -ntmyren@google.com + diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java index b050f5da7928..5bf2c0983a9a 100644 --- a/core/java/android/content/pm/verify/domain/DomainOwner.java +++ b/core/java/android/content/pm/verify/domain/DomainOwner.java @@ -66,16 +66,7 @@ public final class DomainOwner implements Parcelable { * @param packageName * Package name of that owns the domain. * @param overrideable - * Whether or not this owner can be automatically overridden. If all owners for a domain are - * overrideable, then calling - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any - * of the owners are non-overrideable, then - * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} must be called with false to disable all of the other owners before this domain can - * be taken by a new owner through - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)}. + * Whether or not this owner can be automatically overridden. */ @DataClass.Generated.Member public DomainOwner( @@ -98,16 +89,9 @@ public final class DomainOwner implements Parcelable { } /** - * Whether or not this owner can be automatically overridden. If all owners for a domain are - * overrideable, then calling - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any - * of the owners are non-overrideable, then - * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} must be called with false to disable all of the other owners before this domain can - * be taken by a new owner through - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, - * Set, boolean)}. + * Whether or not this owner can be automatically overridden. + * + * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean) */ @DataClass.Generated.Member public boolean isOverrideable() { @@ -205,7 +189,7 @@ public final class DomainOwner implements Parcelable { }; @DataClass.Generated( - time = 1614119379978L, + time = 1614721802044L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java", inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)") diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java index 809587524f58..7c335b1d26dd 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java @@ -94,7 +94,7 @@ public final class DomainVerificationInfo implements Parcelable { private Map<String, Integer> unparcelHostToStateMap(Parcel in) { return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), - DomainVerificationUserSelection.class.getClassLoader()); + DomainVerificationUserState.class.getClassLoader()); } @@ -105,8 +105,7 @@ public final class DomainVerificationInfo implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain - // /DomainVerificationInfo.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -321,7 +320,7 @@ public final class DomainVerificationInfo implements Parcelable { }; @DataClass.Generated( - time = 1613002530369L, + time = 1614721812023L, 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)") diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index 11402afac8b6..f7c81bcffda3 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -25,6 +25,8 @@ import android.annotation.SystemService; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; +import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.UserHandle; import java.util.List; @@ -32,55 +34,63 @@ import java.util.Set; import java.util.UUID; /** - * System service to access the domain verification APIs. + * System service to access domain verification APIs. * - * Allows the approved domain verification - * agent on the device (the sole holder of - * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status - * of domains declared by applications in their AndroidManifest.xml, to allow them to open those - * links inside the app when selected by the user. This is done through querying - * {@link #getDomainVerificationInfo(String)} and calling - * {@link #setDomainVerificationStatus(UUID, Set, int)}. - * - * Also allows the domain preference settings (holder of - * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the - * preferences of the user, when they have chosen to explicitly allow an application to open links. - * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling - * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and - * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. - * - * @hide + * Applications should use {@link #getDomainVerificationUserState(String)} if necessary to + * check if/how they are verified for a domain, which is required starting from platform + * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare + * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against + * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an + * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of + * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the + * caller when using {@link Context#startActivity(Intent)} and similar. */ -@SystemApi @SystemService(Context.DOMAIN_VERIFICATION_SERVICE) -public interface DomainVerificationManager { +public final class DomainVerificationManager { /** - * Extra field name for a {@link DomainVerificationRequest} for the requested packages. - * Passed to an the domain verification agent that handles + * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed + * to an the domain verification agent that handles * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}. + * + * @hide */ - String EXTRA_VERIFICATION_REQUEST = + @SystemApi + public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; /** * No response has been recorded by either the system or any verification agent. + * + * @hide */ - int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; + @SystemApi + public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; - /** The verification agent has explicitly verified the domain at some point. */ - int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; + /** + * The verification agent has explicitly verified the domain at some point. + * + * @hide + */ + @SystemApi + public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; /** - * 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 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. + * + * @hide */ - int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + @SystemApi + public static final int STATE_FIRST_VERIFIER_DEFINED = + DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; - /** @hide */ + /** + * @hide + */ @NonNull - static String stateToDebugString(@DomainVerificationState.State int state) { + public static String stateToDebugString(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_NO_RESPONSE: return "none"; @@ -104,10 +114,13 @@ public interface DomainVerificationManager { } /** - * 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. + * 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. + * + * @hide */ - static boolean isStateVerified(@DomainVerificationState.State int state) { + @SystemApi + public static boolean isStateVerified(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_SUCCESS: case DomainVerificationState.STATE_APPROVED: @@ -126,10 +139,13 @@ public interface DomainVerificationManager { /** * 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. + * this method to determine if a state can be changed without having to be aware of what the new + * state means. + * + * @hide */ - static boolean isStateModifiable(@DomainVerificationState.State int state) { + @SystemApi + public static boolean isStateModifiable(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_NO_RESPONSE: case DomainVerificationState.STATE_SUCCESS: @@ -147,11 +163,12 @@ public interface DomainVerificationManager { } /** - * For determine re-verify policy. This is hidden from the domain verification agent so that - * no behavior is made based on the result. + * For determine re-verify policy. This is hidden from the domain verification agent so that no + * behavior is made based on the result. + * * @hide */ - static boolean isStateDefault(@DomainVerificationState.State int state) { + public static boolean isStateDefault(@DomainVerificationState.State int state) { switch (state) { case DomainVerificationState.STATE_NO_RESPONSE: case DomainVerificationState.STATE_MIGRATED: @@ -168,14 +185,72 @@ public interface DomainVerificationManager { } /** + * @hide + */ + public static final int ERROR_INVALID_DOMAIN_SET = 1; + /** + * @hide + */ + public static final int ERROR_NAME_NOT_FOUND = 2; + + /** + * @hide + */ + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_INVALID_DOMAIN_SET, + ERROR_NAME_NOT_FOUND, + }) + private @interface Error { + } + + private final Context mContext; + + private final IDomainVerificationManager mDomainVerificationManager; + + + /** + * System service to access the domain verification APIs. + * <p> + * Allows the approved domain verification agent on the device (the sole holder of {@link + * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of + * domains declared by applications in their AndroidManifest.xml, to allow them to open those + * links inside the app when selected by the user. This is done through querying {@link + * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID, + * Set, int)}. + * <p> + * Also allows the domain preference settings (holder of + * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) + * to update the preferences of the user, when they have chosen to explicitly allow an + * application to open links. This is done through querying + * {@link #getDomainVerificationUserState(String)} and calling + * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and + * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. + * + * @hide + */ + public DomainVerificationManager(Context context, + IDomainVerificationManager domainVerificationManager) { + mContext = context; + mDomainVerificationManager = domainVerificationManager; + } + + /** * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is * usually a heavy workload and should be done infrequently. * * @return the current snapshot of package names with valid autoVerify URLs. + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - List<String> getValidVerificationPackageNames(); + public List<String> queryValidVerificationPackageNames() { + try { + return mDomainVerificationManager.queryValidVerificationPackageNames(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Retrieves the domain verification state for a given package. @@ -183,61 +258,106 @@ public interface DomainVerificationManager { * @return the data for the package, or null if it does not declare any autoVerify domains * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error * and should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @Nullable @RequiresPermission(anyOf = { android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION }) - DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) - throws NameNotFoundException; + public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) + throws NameNotFoundException { + try { + return mDomainVerificationManager.getDomainVerificationInfo(packageName); + } catch (Exception e) { + Exception converted = rethrow(e, packageName); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** - * Change the verification status of the {@param domains} of the package associated with - * {@param domainSetId}. + * Change the verification status of the {@param domains} of the package associated with {@param + * domainSetId}. * * @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. + * 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. + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - @DomainVerificationState.State int state) throws NameNotFoundException; + public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, + @DomainVerificationState.State int state) throws NameNotFoundException { + try { + mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), + new DomainSet(domains), state); + } catch (Exception e) { + Exception converted = rethrow(e, domainSetId); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** - * TODO(b/178525735): This documentation is incorrect in the context of UX changes. - * Change whether the given {@param packageName} is allowed to automatically open verified - * HTTP/HTTPS domains. The final state is determined along with the verification status for the - * specific domain being opened and other system state. An app with this enabled is not - * guaranteed to be the sole link handler for its domains. + * Change whether the given packageName is allowed to handle BROWSABLE and DEFAULT category web + * (HTTP/HTTPS) {@link Intent} Activity open requests. The final state is determined along with + * the verification status for the specific domain being opened and other system state. An app + * with this enabled is not guaranteed to be the sole link handler for its domains. + * <p> + * By default, all apps are allowed to open links. Users must disable them explicitly. * - * By default, all apps are allowed to open verified links. Users must disable them explicitly. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed) - throws NameNotFoundException; + public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, + boolean allowed) throws NameNotFoundException { + try { + mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, + allowed, mContext.getUserId()); + } catch (Exception e) { + Exception converted = rethrow(e, packageName); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** * Update the recorded user selection for the given {@param domains} for the given {@param * domainSetId}. This state is recorded for the lifetime of a domain for a package on device, * and will never be reset by the system short of an app data clear. - * + * <p> * This state is stored per device user. If another user needs to be changed, the appropriate - * permissions must be acquired and - * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. - * + * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should + * be used. + * <p> * Enabling an unverified domain will allow an application to open it, but this can only occur * if no other app on the device is approved for a higher approval level. This can queried * using {@link #getOwnersForDomain(String)}. @@ -255,33 +375,55 @@ public interface DomainVerificationManager { * @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. + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - void setDomainVerificationUserSelection(@NonNull UUID domainSetId, - @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException; + public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, + @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException { + try { + mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), + new DomainSet(domains), enabled, mContext.getUserId()); + } catch (Exception e) { + Exception converted = rethrow(e, domainSetId); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** * Retrieve the user selection data for the given {@param packageName} and the current user. - * It is the responsibility of the caller to ensure that the - * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls. - * - * This state is stored per device user. If another user needs to be accessed, the appropriate - * permissions must be acquired and - * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. * * @param packageName The app to query state for. - * @return the user selection verification data for the given package for the current user, - * or null if the package does not declare any HTTP/HTTPS domains. + * @return the user selection verification data for the given package for the current user, or + * null if the package does not declare any HTTP/HTTPS domains. */ @Nullable - @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName) - throws NameNotFoundException; + public DomainVerificationUserState getDomainVerificationUserState( + @NonNull String packageName) throws NameNotFoundException { + try { + return mDomainVerificationManager.getDomainVerificationUserState(packageName, + mContext.getUserId()); + } catch (Exception e) { + Exception converted = rethrow(e, packageName); + if (converted instanceof NameNotFoundException) { + throw (NameNotFoundException) converted; + } else if (converted instanceof RuntimeException) { + throw (RuntimeException) converted; + } else { + throw new RuntimeException(converted); + } + } + } /** * For the given domain, return all apps which are approved to open it in a @@ -291,21 +433,65 @@ public interface DomainVerificationManager { * * By default the list will be returned ordered from lowest to highest * priority. + * + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - List<DomainOwner> getOwnersForDomain(@NonNull String domain); + public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { + try { + return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + 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; + 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; + } + } 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. + * {@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 */ - class InvalidDomainSetException extends IllegalArgumentException { + public static class InvalidDomainSetException extends IllegalArgumentException { public static final int REASON_ID_NULL = 1; public static final int REASON_ID_INVALID = 2; @@ -313,7 +499,9 @@ public interface DomainVerificationManager { public static final int REASON_UNKNOWN_DOMAIN = 4; public static final int REASON_UNABLE_TO_APPROVE = 5; - /** @hide */ + /** + * @hide + */ @IntDef({ REASON_ID_NULL, REASON_ID_INVALID, @@ -352,7 +540,9 @@ public interface DomainVerificationManager { @Nullable private final String mPackageName; - /** @hide */ + /** + * @hide + */ public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName, @Reason int reason) { super(buildMessage(domainSetId, packageName, reason)); diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java deleted file mode 100644 index 8b9865c2b436..000000000000 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.pm.verify.domain; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.RemoteException; -import android.os.ServiceSpecificException; - -import java.util.List; -import java.util.Set; -import java.util.UUID; - -/** - * @hide - */ -@SuppressWarnings("RedundantThrows") -public class DomainVerificationManagerImpl implements DomainVerificationManager { - - public static final int ERROR_INVALID_DOMAIN_SET = 1; - public static final int ERROR_NAME_NOT_FOUND = 2; - - @IntDef(prefix = { "ERROR_" }, value = { - ERROR_INVALID_DOMAIN_SET, - ERROR_NAME_NOT_FOUND, - }) - private @interface Error { - } - - private final Context mContext; - - private final IDomainVerificationManager mDomainVerificationManager; - - public DomainVerificationManagerImpl(Context context, - IDomainVerificationManager domainVerificationManager) { - mContext = context; - mDomainVerificationManager = domainVerificationManager; - } - - @NonNull - @Override - public List<String> getValidVerificationPackageNames() { - try { - return mDomainVerificationManager.getValidVerificationPackageNames(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - @Nullable - @Override - public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) - throws NameNotFoundException { - try { - return mDomainVerificationManager.getDomainVerificationInfo(packageName); - } catch (Exception e) { - Exception converted = rethrow(e, packageName); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Override - public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - int state) throws IllegalArgumentException, NameNotFoundException { - try { - mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), - new DomainSet(domains), state); - } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Override - public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, - boolean allowed) throws NameNotFoundException { - try { - mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, - allowed, mContext.getUserId()); - } catch (Exception e) { - Exception converted = rethrow(e, packageName); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Override - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, - @NonNull Set<String> domains, boolean enabled) - throws IllegalArgumentException, NameNotFoundException { - try { - mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), - new DomainSet(domains), enabled, mContext.getUserId()); - } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @Nullable - @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( - @NonNull String packageName) throws NameNotFoundException { - try { - return mDomainVerificationManager.getDomainVerificationUserSelection(packageName, - mContext.getUserId()); - } catch (Exception e) { - Exception converted = rethrow(e, packageName); - if (converted instanceof NameNotFoundException) { - throw (NameNotFoundException) converted; - } else if (converted instanceof RuntimeException) { - throw (RuntimeException) converted; - } else { - throw new RuntimeException(converted); - } - } - } - - @NonNull - @Override - public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { - try { - return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - 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; - 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; - } - } else if (exception instanceof RemoteException) { - return ((RemoteException) exception).rethrowFromSystemServer(); - } else { - return exception; - } - } -} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl index ddb5ef85382a..94690c1dae93 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.aidl @@ -16,4 +16,4 @@ package android.content.pm.verify.domain; -parcelable DomainVerificationUserSelection; +parcelable DomainVerificationUserState; diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java index d23f5f133841..1e60abb30011 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -29,39 +30,36 @@ import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import java.util.Map; -import java.util.Set; import java.util.UUID; /** * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a * package in its manifest, whether or not they were marked for auto verification. * <p> - * By default, all apps are allowed to automatically open links with domains that they've - * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user - * can decide to disable this, disallowing the application from opening all links. Note that the - * toggle affects <b>all</b> links and is not based on the verification state of the domains. + * Applications should use {@link #getHostToStateMap()} if necessary to + * check if/how they are verified for a domain, which is required starting from platform + * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare + * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against + * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an + * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of + * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the + * caller when using {@link Context#startActivity(Intent)} and similar. + * <p> + * By default, all apps are allowed to automatically open links for the above case for domains that + * they've successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. + * The user can decide to disable this, disallowing the application from opening all links. Note + * that the toggle affects <b>all</b> links and is not based on the verification state of the + * domains. * <p> * Assuming the toggle is enabled, the user can also select additional unverified domains to grant * to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single * application can be approved for a domain unless the applications are both approved. If another * application is approved, the user will not be allowed to enable the domain. - * <p> - * These values can be changed through the - * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)} APIs. - * <p> - * Note that because state is per user, if a different user needs to be changed, one will need to - * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link - * android.Manifest.permission#INTERACT_ACROSS_USERS} permission. - * - * @hide */ -@SystemApi @SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, genEqualsHashCode = true, genHiddenConstDefs = true) -public final class DomainVerificationUserSelection implements Parcelable { +public final class DomainVerificationUserState implements Parcelable { /** * The domain is unverified and unselected, and the application is unable to open web links @@ -70,9 +68,8 @@ public final class DomainVerificationUserSelection implements Parcelable { public static final int DOMAIN_STATE_NONE = 0; /** - * The domain has been selected through the - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)} - * API, under the assumption it has not been reset by the system. + * The domain has been selected by the user. This may be reset to {@link #DOMAIN_STATE_NONE} if + * another application is selected or verified for the same domain. */ public static final int DOMAIN_STATE_SELECTED = 1; @@ -119,7 +116,16 @@ public final class DomainVerificationUserSelection implements Parcelable { @NonNull private Map<String, Integer> unparcelHostToStateMap(Parcel in) { return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), - DomainVerificationUserSelection.class.getClassLoader()); + DomainVerificationUserState.class.getClassLoader()); + } + + /** + * @see DomainVerificationInfo#getIdentifier + * @hide + */ + @SystemApi + public @NonNull UUID getIdentifier() { + return mIdentifier; } @@ -130,7 +136,7 @@ public final class DomainVerificationUserSelection implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -162,7 +168,7 @@ public final class DomainVerificationUserSelection implements Parcelable { } /** - * Creates a new DomainVerificationUserSelection. + * Creates a new DomainVerificationUserState. * * @param packageName * The package name that this data corresponds to. @@ -175,7 +181,7 @@ public final class DomainVerificationUserSelection implements Parcelable { * @hide */ @DataClass.Generated.Member - public DomainVerificationUserSelection( + public DomainVerificationUserState( @NonNull UUID identifier, @NonNull String packageName, @NonNull UserHandle user, @@ -201,14 +207,6 @@ public final class DomainVerificationUserSelection implements Parcelable { } /** - * @see DomainVerificationInfo#getIdentifier - */ - @DataClass.Generated.Member - public @NonNull UUID getIdentifier() { - return mIdentifier; - } - - /** * The package name that this data corresponds to. */ @DataClass.Generated.Member @@ -246,7 +244,7 @@ public final class DomainVerificationUserSelection implements Parcelable { // You can override field toString logic by defining methods like: // String fieldNameToString() { ... } - return "DomainVerificationUserSelection { " + + return "DomainVerificationUserState { " + "identifier = " + mIdentifier + ", " + "packageName = " + mPackageName + ", " + "user = " + mUser + ", " + @@ -259,13 +257,13 @@ public final class DomainVerificationUserSelection implements Parcelable { @DataClass.Generated.Member public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... } + // boolean fieldNameEquals(DomainVerificationUserState other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") - DomainVerificationUserSelection that = (DomainVerificationUserSelection) o; + DomainVerificationUserState that = (DomainVerificationUserState) o; //noinspection PointlessBooleanExpression return true && java.util.Objects.equals(mIdentifier, that.mIdentifier) @@ -323,7 +321,7 @@ public final class DomainVerificationUserSelection implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) { + /* package-private */ DomainVerificationUserState(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } @@ -354,24 +352,24 @@ public final class DomainVerificationUserSelection implements Parcelable { } @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<DomainVerificationUserSelection> CREATOR - = new Parcelable.Creator<DomainVerificationUserSelection>() { + public static final @NonNull Parcelable.Creator<DomainVerificationUserState> CREATOR + = new Parcelable.Creator<DomainVerificationUserState>() { @Override - public DomainVerificationUserSelection[] newArray(int size) { - return new DomainVerificationUserSelection[size]; + public DomainVerificationUserState[] newArray(int size) { + return new DomainVerificationUserState[size]; } @Override - public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) { - return new DomainVerificationUserSelection(in); + public DomainVerificationUserState createFromParcel(@NonNull Parcel in) { + return new DomainVerificationUserState(in); } }; @DataClass.Generated( - time = 1613683603297L, + time = 1614721840152L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java", - inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\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 android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection 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)") + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java", + inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\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 android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\npublic @android.annotation.SystemApi @android.annotation.NonNull java.util.UUID getIdentifier()\nclass DomainVerificationUserState 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/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index 701af320fb01..332b92544581 100644 --- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl +++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl @@ -19,7 +19,7 @@ package android.content.pm.verify.domain; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; -import android.content.pm.verify.domain.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import java.util.List; /** @@ -28,13 +28,13 @@ import java.util.List; */ interface IDomainVerificationManager { - List<String> getValidVerificationPackageNames(); + List<String> queryValidVerificationPackageNames(); @nullable DomainVerificationInfo getDomainVerificationInfo(String packageName); @nullable - DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName, + DomainVerificationUserState getDomainVerificationUserState(String packageName, int userId); @nullable diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 2d381eb85e90..de48ed75746d 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -22,6 +22,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.om.OverlayableInfo; import android.content.res.loader.AssetsProvider; import android.content.res.loader.ResourcesProvider; +import android.text.TextUtils; import com.android.internal.annotations.GuardedBy; @@ -344,7 +345,14 @@ public final class ApkAssets { @UnsupportedAppUsage public @NonNull String getAssetPath() { synchronized (this) { - return nativeGetAssetPath(mNativePtr); + return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr)); + } + } + + /** @hide */ + public @NonNull String getDebugName() { + synchronized (this) { + return nativeGetDebugName(mNativePtr); } } @@ -422,7 +430,7 @@ public final class ApkAssets { @Override public String toString() { - return "ApkAssets{path=" + getAssetPath() + "}"; + return "ApkAssets{path=" + getDebugName() + "}"; } /** @@ -450,6 +458,7 @@ public final class ApkAssets { @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; private static native @NonNull String nativeGetAssetPath(long ptr); + private static native @NonNull String nativeGetDebugName(long ptr); private static native long nativeGetStringBlock(long ptr); private static native boolean nativeIsUpToDate(long ptr); private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index fd98d37bb7f4..31d1b69182f1 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -62,10 +62,13 @@ public interface BiometricAuthenticator { * @hide */ int TYPE_FACE = 1 << 3; - @IntDef({TYPE_NONE, + + @IntDef(flag = true, value = { + TYPE_NONE, TYPE_CREDENTIAL, TYPE_FINGERPRINT, - TYPE_IRIS}) + TYPE_IRIS + }) @Retention(RetentionPolicy.SOURCE) @interface Modality {} diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 5b28e0035b09..1fdce5e773b1 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -193,15 +194,15 @@ public class BiometricManager { int DEVICE_CREDENTIAL = 1 << 15; } - private final Context mContext; - private final IAuthService mService; + @NonNull private final Context mContext; + @NonNull private final IAuthService mService; /** * @hide * @param context * @param service */ - public BiometricManager(Context context, IAuthService service) { + public BiometricManager(@NonNull Context context, @NonNull IAuthService service) { mContext = context; mService = service; } @@ -274,7 +275,8 @@ public class BiometricManager { */ @Deprecated @RequiresPermission(USE_BIOMETRIC) - public @BiometricError int canAuthenticate() { + @BiometricError + public int canAuthenticate() { return canAuthenticate(Authenticators.BIOMETRIC_WEAK); } @@ -304,7 +306,8 @@ public class BiometricManager { * authenticators can currently be used (enrolled and available). */ @RequiresPermission(USE_BIOMETRIC) - public @BiometricError int canAuthenticate(@Authenticators.Types int authenticators) { + @BiometricError + public int canAuthenticate(@Authenticators.Types int authenticators) { return canAuthenticate(mContext.getUserId(), authenticators); } @@ -312,8 +315,10 @@ public class BiometricManager { * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public @BiometricError int canAuthenticate(int userId, - @Authenticators.Types int authenticators) { + @BiometricError + public int canAuthenticate( + int userId, @Authenticators.Types int authenticators) { + if (mService != null) { try { final String opPackageName = mContext.getOpPackageName(); @@ -322,7 +327,7 @@ public class BiometricManager { throw e.rethrowFromSystemServer(); } } else { - Slog.w(TAG, "hasEnrolledBiometrics(): Service not connected"); + Slog.w(TAG, "canAuthenticate(): Service not connected"); return BIOMETRIC_ERROR_HW_UNAVAILABLE; } } @@ -404,5 +409,115 @@ public class BiometricManager { } } + /** + * Provides a localized string that may be used as the label for a button that invokes + * {@link BiometricPrompt}. + * + * <p>When possible, this method should use the given authenticator requirements to more + * precisely specify the authentication type that will be used. For example, if + * <strong>Class 3</strong> biometric authentication is requested on a device with a + * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor, the + * returned string should indicate that fingerprint authentication will be used. + * + * <p>This method should also try to specify which authentication method(s) will be used in + * practice when multiple authenticators meet the given requirements. For example, if biometric + * authentication is requested on a device with both face and fingerprint sensors but the user + * has selected face as their preferred method, the returned string should indicate that face + * authentication will be used. + * + * @param authenticators A bit field representing the types of {@link Authenticators} that may + * be used for authentication. + * @return The label for a button that invokes {@link BiometricPrompt} for authentication. + */ + @RequiresPermission(USE_BIOMETRIC) + @Nullable + public CharSequence getButtonLabel(@Authenticators.Types int authenticators) { + if (mService != null) { + final int userId = mContext.getUserId(); + final String opPackageName = mContext.getOpPackageName(); + try { + return mService.getButtonLabel(userId, opPackageName, authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "getButtonLabel(): Service not connected"); + return null; + } + } + + /** + * Provides a localized string that may be shown while the user is authenticating with + * {@link BiometricPrompt}. + * + * <p>When possible, this method should use the given authenticator requirements to more + * precisely specify the authentication type that will be used. For example, if + * <strong>Class 3</strong> biometric authentication is requested on a device with a + * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor, the + * returned string should indicate that fingerprint authentication will be used. + * + * <p>This method should also try to specify which authentication method(s) will be used in + * practice when multiple authenticators meet the given requirements. For example, if biometric + * authentication is requested on a device with both face and fingerprint sensors but the user + * has selected face as their preferred method, the returned string should indicate that face + * authentication will be used. + * + * @param authenticators A bit field representing the types of {@link Authenticators} that may + * be used for authentication. + * @return The label for a button that invokes {@link BiometricPrompt} for authentication. + */ + @RequiresPermission(USE_BIOMETRIC) + @Nullable + public CharSequence getPromptMessage(@Authenticators.Types int authenticators) { + if (mService != null) { + final int userId = mContext.getUserId(); + final String opPackageName = mContext.getOpPackageName(); + try { + return mService.getPromptMessage(userId, opPackageName, authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "getPromptMessage(): Service not connected"); + return null; + } + } + + /** + * Provides a localized string that may be shown as the title for an app setting that enables + * biometric authentication. + * + * <p>When possible, this method should use the given authenticator requirements to more + * precisely specify the authentication type that will be used. For example, if + * <strong>Class 3</strong> biometric authentication is requested on a device with a + * <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor, the + * returned string should indicate that fingerprint authentication will be used. + * + * <p>This method should <em>not</em> try to specify which authentication method(s) will be used + * in practice when multiple authenticators meet the given requirements. For example, if + * biometric authentication is requested on a device with both face and fingerprint sensors, the + * returned string should indicate that either face or fingerprint authentication may be used, + * regardless of whether the user has enrolled or selected either as their preferred method. + * + * @param authenticators A bit field representing the types of {@link Authenticators} that may + * be used for authentication. + * @return The label for a button that invokes {@link BiometricPrompt} for authentication. + */ + @RequiresPermission(USE_BIOMETRIC) + @Nullable + public CharSequence getSettingName(@Authenticators.Types int authenticators) { + if (mService != null) { + final int userId = mContext.getUserId(); + final String opPackageName = mContext.getOpPackageName(); + try { + return mService.getSettingName(userId, opPackageName, authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "getSettingName(): Service not connected"); + return null; + } + } } diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index d8c9dbc849a9..1472bb940be5 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -68,4 +68,16 @@ interface IAuthService { // the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore // land as SIDs, and are used during key generation. long[] getAuthenticatorIds(); + + // 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); + + // Provides a localized string that may be shown while the user is authenticating with + // BiometricPrompt. + CharSequence getPromptMessage(int userId, String opPackageName, int authenticators); + + // Provides a localized string that may be shown as the title for an app setting that enables + // biometric authentication. + CharSequence getSettingName(int userId, String opPackageName, int authenticators); } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 24331863a05f..6d8bf0fb5543 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -75,4 +75,10 @@ interface IBiometricService { long[] getAuthenticatorIds(int callingUserId); int getCurrentStrength(int sensorId); + + // Returns a bit field of the modality (or modalities) that are will be used for authentication. + int getCurrentModality(String opPackageName, int userId, int callingUserId, int authenticators); + + // Returns a bit field of the authentication modalities that are supported by this device. + int getSupportedModalities(int authenticators); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 16ab900dee06..07ebbaff67ea 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -24,13 +24,11 @@ import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; import android.hardware.camera2.params.SessionConfiguration; -import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.TypeReference; import android.os.Build; import android.util.Rational; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -641,27 +639,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri */ @NonNull public Set<String> getPhysicalCameraIds() { - int[] availableCapabilities = get(REQUEST_AVAILABLE_CAPABILITIES); - if (availableCapabilities == null) { - throw new AssertionError("android.request.availableCapabilities must be non-null " - + "in the characteristics"); - } - - if (!ArrayUtils.contains(availableCapabilities, - REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { - return Collections.emptySet(); - } - byte[] physicalCamIds = get(LOGICAL_MULTI_CAMERA_PHYSICAL_IDS); - - String physicalCamIdString = null; - try { - physicalCamIdString = new String(physicalCamIds, "UTF-8"); - } catch (java.io.UnsupportedEncodingException e) { - throw new AssertionError("android.logicalCam.physicalIds must be UTF-8 string"); - } - String[] physicalCameraIdArray = physicalCamIdString.split("\0"); - - return Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(physicalCameraIdArray))); + return mProperties.getPhysicalCameraIds(); } /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ @@ -2931,6 +2909,74 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.util.Size>("android.scaler.defaultSecureImageSize", android.util.Size.class); /** + * <p>The available multi-resolution stream configurations that this + * physical camera device supports + * (i.e. format, width, height, output/input stream).</p> + * <p>This list contains a subset of the parent logical camera's multi-resolution stream + * configurations which belong to this physical camera, and it will advertise and will only + * advertise the maximum supported resolutions for a particular format.</p> + * <p>If this camera device isn't a physical camera device constituting a logical camera, + * but a standalone ULTRA_HIGH_RESOLUTION_SENSOR camera, this field represents the + * multi-resolution input/output stream configurations of default mode and max resolution + * modes. The sizes will be the maximum resolution of a particular format for default mode + * and max resolution mode.</p> + * <p>This field will only be advertised if the device is a physical camera of a + * logical multi-camera device or an ultra high resolution sensor camera. For a logical + * multi-camera, the camera API will derive the logical camera’s multi-resolution stream + * configurations from all physical cameras. For an ultra high resolution sensor camera, this + * is used directly as the camera’s multi-resolution stream configurations.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.scaler.physicalCameraMultiResolutionStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>The multi-resolution stream configurations supported by this logical camera + * or ultra high resolution sensor camera device.</p> + * <p>Multi-resolution streams can be used by a LOGICAL_MULTI_CAMERA or an + * ULTRA_HIGH_RESOLUTION_SENSOR camera where the images sent or received can vary in + * resolution per frame. This is useful in cases where the camera device's effective full + * resolution changes depending on factors such as the current zoom level, lighting + * condition, focus distance, or pixel mode.</p> + * <ul> + * <li>For a logical multi-camera implementing optical zoom, at different zoom level, a + * different physical camera may be active, resulting in different full-resolution image + * sizes.</li> + * <li>For an ultra high resolution camera, depending on whether the camera operates in default + * mode, or maximum resolution mode, the output full-size images may be of either binned + * resolution or maximum resolution.</li> + * </ul> + * <p>To use multi-resolution output streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats }. + * A {@link android.hardware.camera2.MultiResolutionImageReader } can then be created for a + * supported format with the MultiResolutionStreamInfo group queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo }.</p> + * <p>If a camera device supports multi-resolution output streams for a particular format, for + * each of its mandatory stream combinations, the camera device will support using a + * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to + * {@link android.hardware.camera2.CameraDevice#createCaptureSession } for additional details.</p> + * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }. + * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with + * the input MultiResolutionStreamInfo group, queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputInfo }.</p> + * <p>If a camera device supports multi-resolution {@code YUV} input and multi-resolution + * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution + * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported + * multi-resolution output stream formats. Refer to + * {@link android.hardware.camera2.CameraDevice#createCaptureSession } for + * details about the additional mandatory stream combinations in this case.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP = + new Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap>("android.scaler.multiResolutionStreamConfigurationMap", android.hardware.camera2.params.MultiResolutionStreamConfigurationMap.class); + + /** * <p>The area of the image sensor which corresponds to active pixels after any geometric * distortion correction has been applied.</p> * <p>This is the rectangle representing the size of the active region of the sensor (i.e. diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index ac6ba0a4ac58..af48b71f9962 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -770,6 +770,8 @@ public abstract class CameraDevice implements AutoCloseable { * streams with {@code Y8} in all guaranteed stream combinations for the device's hardware level * and capabilities.</p> * + * <p>Clients can access the above mandatory stream combination tables via + * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> * * <p>Devices capable of outputting HEIC formats ({@link StreamConfigurationMap#getOutputFormats} * contains {@link android.graphics.ImageFormat#HEIC}) will support substituting {@code JPEG} @@ -777,8 +779,33 @@ public abstract class CameraDevice implements AutoCloseable { * level and capabilities. Calling createCaptureSession with both JPEG and HEIC outputs is not * supported.</p> * - * <p>Clients can access the above mandatory stream combination tables via - * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> + * <p>Devices capable of multi-resolution output for a particular format ( + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo} + * returns a non-empty list) support using {@link MultiResolutionImageReader} for MAXIMUM + * resolution streams of that format for all mandatory stream combinations. For example, + * if a LIMITED camera device supports multi-resolution output streams for both {@code JPEG} and + * {@code PRIVATE}, in addition to the stream configurations + * in the LIMITED and Legacy table above, the camera device supports the following guaranteed + * stream combinations ({@code MULTI_RES} in the Max size column refers to a {@link + * MultiResolutionImageReader} created based on the variable max resolutions supported): + * + * <table> + * <tr><th colspan="7">LEGACY-level additional guaranteed combinations with MultiResolutionoutputs</th></tr> + * <tr> <th colspan="2" id="rb">Target 1</th> <th colspan="2" id="rb">Target 2</th> <th colspan="2" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> + * <tr> <th>Type</th><th id="rb">Max size</th> <th>Type</th><th id="rb">Max size</th> <th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td colspan="2" id="rb"></td> <td colspan="2" id="rb"></td> <td>Simple preview, GPU video processing, or no-preview video recording.</td> </tr> + * <tr> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td colspan="2" id="rb"></td> <td colspan="2" id="rb"></td> <td>No-viewfinder still image capture.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td colspan="2" id="rb"></td> <td>Standard still imaging.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>Still capture plus in-app processing.</td> </tr> + * </table><br> + * <table> + * <tr><th colspan="7">LIMITED-level additional guaranteed configurations with MultiResolutionoutputs</th></tr> + * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>Two-input in-app processing with still capture.</td> </tr> + * </table><br> + * The same logic applies to other hardware levels and capabilities. + * </p> * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for @@ -939,6 +966,32 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * + * <p>If a camera device supports multi-resolution {@code YUV} input and multi-resolution + * {@code YUV} output or supports multi-resolution {@code PRIVATE} input and multi-resolution + * {@code PRIVATE} output, the additional mandatory stream combinations for LIMITED and FULL devices are listed + * below ({@code MULTI_RES} in the Max size column refers to a + * {@link MultiResolutionImageReader} for output, and a multi-resolution + * {@link InputConfiguration} for input): + * <table> + * <tr><th colspan="11">LIMITED-level additional guaranteed configurations for creating a reprocessable capture session with multi-resolution input and multi-resolution outputs<br>({@code PRIV} input is guaranteed only if PRIVATE reprocessing is supported. {@code YUV} input is guaranteed only if YUV reprocessing is supported)</th></tr> + * <tr><th colspan="2" id="rb">Input</th><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th><th colspan="2" id="rb">Target 4</th><th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td></td><td id="rb"></td> <td>No-viewfinder still image reprocessing.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>ZSL(Zero-Shutter-Lag) still imaging.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>ZSL still and in-app processing imaging.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>ZSL in-app processing with still capture.</td> </tr> + * </table><br> + * <table> + * <tr><th colspan="11">FULL-level additional guaranteed configurations for creating a reprocessable capture session with multi-resolution input and multi-resolution outputs<br>({@code PRIV} input is guaranteed only if PRIVATE reprocessing is supported. {@code YUV} input is guaranteed only if YUV reprocessing is supported)</th></tr> + * <tr><th colspan="2" id="rb">Input</th><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th><th colspan="2" id="rb">Target 4</th><th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>Maximum-resolution ZSL in-app processing with regular preview.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code MULTI_RES}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td></td><td id="rb"></td> <td>Maximum-resolution two-input ZSL in-app processing.</td> </tr> + * <tr> <td>{@code PRIV}/{@code YUV}</td><td id="rb">{@code MULTI_RES}</td> <td>Same as input</td><td id="rb">{@code MULTI_RES}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MULTI_RES}</td> <td>ZSL still capture and in-app processing.</td> </tr> + * </table><br> + * No additional mandatory stream combinations for RAW capability and LEVEL-3 hardware level. + * </p> + * * <h3>Constrained high-speed recording</h3> * * <p>The application can use a diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 3e0e3f62574f..a3c6f2f1eafd 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -31,6 +31,7 @@ import android.hardware.camera2.impl.CameraDeviceImpl; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.utils.CameraIdAndSessionConfiguration; import android.hardware.camera2.utils.ConcurrentCameraIdCombination; import android.hardware.display.DisplayManager; @@ -51,6 +52,7 @@ import android.view.Display; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -372,6 +374,47 @@ public final class CameraManager { } /** + * Get all physical cameras' multi-resolution stream configuration map + * + * <p>For a logical multi-camera, query the map between physical camera id and + * the physical camera's multi-resolution stream configuration. This map is in turn + * combined to form the logical camera's multi-resolution stream configuration map.</p> + */ + private Map<String, StreamConfiguration[]> getPhysicalCameraMultiResolutionConfigs( + CameraMetadataNative info, ICameraService cameraService) + throws CameraAccessException { + HashMap<String, StreamConfiguration[]> multiResolutionStreamConfigurations = + new HashMap<String, StreamConfiguration[]>(); + + // Query the characteristics of all physical sub-cameras, and combine the multi-resolution + // stream configurations. Note that framework derived formats such as HEIC and DEPTH_JPEG + // aren't supported as multi-resolution input or output formats. + Set<String> physicalCameraIds = info.getPhysicalCameraIds(); + try { + for (String physicalCameraId : physicalCameraIds) { + CameraMetadataNative physicalCameraInfo = + cameraService.getCameraCharacteristics(physicalCameraId); + StreamConfiguration[] configs = physicalCameraInfo.get( + CameraCharacteristics. + SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS); + if (configs != null) { + multiResolutionStreamConfigurations.put(physicalCameraId, configs); + } + } + + // TODO: If this is an ultra high resolution sensor camera, combine the multi-resolution + // stream combination from "info" as well. + } catch (RemoteException e) { + ServiceSpecificException sse = new ServiceSpecificException( + ICameraService.ERROR_DISCONNECTED, + "Camera service is currently unavailable"); + throwAsPublicException(sse); + } + + return multiResolutionStreamConfigurations; + } + + /** * <p>Query the capabilities of a camera device. These capabilities are * immutable for a given camera.</p> * @@ -418,12 +461,19 @@ public final class CameraManager { } catch (NumberFormatException e) { Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer"); } + boolean hasConcurrentStreams = CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId); info.setHasMandatoryConcurrentStreams(hasConcurrentStreams); info.setDisplaySize(displaySize); - characteristics = new CameraCharacteristics(info); + Map<String, StreamConfiguration[]> multiResolutionSizeMap = + getPhysicalCameraMultiResolutionConfigs(info, cameraService); + if (multiResolutionSizeMap.size() > 0) { + info.setMultiResolutionStreamConfigurationMap(multiResolutionSizeMap); + } + + characteristics = new CameraCharacteristics(info); } catch (ServiceSpecificException e) { throwAsPublicException(e); } catch (RemoteException e) { diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java new file mode 100644 index 000000000000..c592f19bc45c --- /dev/null +++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2; + +import android.annotation.CallbackExecutor; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.ImageFormat; +import android.graphics.ImageFormat.Format; +import android.hardware.HardwareBuffer; +import android.hardware.HardwareBuffer.Usage; +import android.media.Image; +import android.media.ImageReader; +import android.hardware.camera2.params.MultiResolutionStreamInfo; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.Surface; + + +import java.nio.NioUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * <p>The MultiResolutionImageReader class wraps a group of {@link ImageReader ImageReaders} with + * the same format and different sizes, source camera Id, or camera sensor modes.</p> + * + * <p>The main use case of this class is for a + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical + * multi-camera} or an ultra high resolution sensor camera to output variable-size images. For a + * logical multi-camera which implements optical zoom, different physical cameras may have different + * maximum resolutions. As a result, when the camera device switches between physical cameras + * depending on zoom ratio, the maximum resolution for a particular format may change. For an + * ultra high resolution sensor camera, the camera device may deem it better or worse to run in + * maximum resolution mode / default mode depending on lighting conditions. So the application may + * choose to let the camera device decide on its behalf.</p> + * + * <p>MultiResolutionImageReader should be used for a camera device only if the camera device + * supports multi-resolution output stream by advertising the specified output format in {@link + * CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP}.</p> + * + * <p>To acquire images from the MultiResolutionImageReader, the application must use the + * {@link ImageReader} object passed by + * {@link ImageReader.OnImageAvailableListener#onImageAvailable} callback to call + * {@link ImageReader#acquireNextImage} or {@link ImageReader#acquireLatestImage}. The application + * must not use the {@link ImageReader} passed by an {@link + * ImageReader.OnImageAvailableListener#onImageAvailable} callback to acquire future images + * because future images may originate from a different {@link ImageReader} contained within the + * {@code MultiResolutionImageReader}.</p> + * + * + * @see ImageReader + * @see android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP + */ +public class MultiResolutionImageReader implements AutoCloseable { + + private static final String TAG = "MultiResolutionImageReader"; + + /** + * <p> + * Create a new multi-resolution reader based on a group of camera stream properties returned + * by a camera device. + * </p> + * <p> + * The valid size and formats depend on the camera characteristics. + * {@code MultiResolutionImageReader} for an image format is supported by the camera device if + * the format is in the supported multi-resolution output stream formats returned by + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. + * If the image format is supported, the {@code MultiResolutionImageReader} object can be + * created with the {@code streams} objects returned by + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}. + * </p> + * <p> + * The {@code maxImages} parameter determines the maximum number of + * {@link Image} objects that can be be acquired from each of the {@code ImageReader} + * within the {@code MultiResolutionImageReader}. However, requesting more buffers will + * use up more memory, so it is important to use only the minimum number necessary. The + * application is strongly recommended to acquire no more than {@code maxImages} images + * from all of the internal ImageReader objects combined. By keeping track of the number of + * acquired images for the MultiResolutionImageReader, the application doesn't need to do the + * bookkeeping for each internal ImageReader returned from {@link + * ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback. + * </p> + * <p> + * Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex + * configuration sequence. Instead of passing the same surface to OutputConfiguration and + * CaptureRequest, the + * {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput} + * call needs to be used to create the OutputConfigurations for session creation, and then + * {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for + * CaptureRequest}. + * </p> + * @param streams The group of multi-resolution stream info, which is used to create + * a multi-resolution reader containing a number of ImageReader objects. Each + * ImageReader object represents a multi-resolution stream in the group. + * @param format The format of the Image that this multi-resolution reader will produce. + * This must be one of the {@link android.graphics.ImageFormat} or + * {@link android.graphics.PixelFormat} constants. Note that not all formats are + * supported, like ImageFormat.NV21. The supported multi-resolution + * reader format can be queried by {@link + * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. + * @param maxImages The maximum number of images the user will want to + * access simultaneously. This should be as small as possible to + * limit memory use. Once maxImages images are obtained by the + * user from any given internal ImageReader, one of them has to be released before + * a new Image will become available for access through the ImageReader's + * {@link ImageReader#acquireLatestImage()} or + * {@link ImageReader#acquireNextImage()}. Must be greater than 0. + * @see Image + * @see + * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP + * @see + * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap + */ + public static @NonNull MultiResolutionImageReader newInstance( + @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; + + if (streams == null || streams.size() <= 1) { + throw new IllegalArgumentException( + "The streams info collection must contain at least 2 entries"); + } + if (mMaxImages < 1) { + throw new IllegalArgumentException( + "Maximum outstanding image count must be at least 1"); + } + + if (format == ImageFormat.NV21) { + throw new IllegalArgumentException( + "NV21 format is not supported"); + } + + int numImageReaders = streams.size(); + mReaders = new ImageReader[numImageReaders]; + mStreamInfo = new MultiResolutionStreamInfo[numImageReaders]; + int index = 0; + for (MultiResolutionStreamInfo streamInfo : streams) { + mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(), + streamInfo.getHeight(), format, maxImages); + mStreamInfo[index] = streamInfo; + index++; + } + } + + /** + * Set onImageAvailableListener callback. + * + * <p>This function sets the onImageAvailableListener for all the internal + * {@link ImageReader} objects.</p> + * + * <p>For a multi-resolution ImageReader, the timestamps of images acquired in + * onImageAvailable callback from different internal ImageReaders may become + * out-of-order due to the asynchronous callbacks between the different resolution + * image queues.</p> + * + * @param listener + * The listener that will be run. + * @param executor + * The executor which will be used when invoking the callback. + */ + @SuppressLint({"ExecutorRegistration", "SamShouldBeLast"}) + public void setOnImageAvailableListener( + @Nullable ImageReader.OnImageAvailableListener listener, + @Nullable @CallbackExecutor Executor executor) { + for (int i = 0; i < mReaders.length; i++) { + mReaders[i].setOnImageAvailableListenerWithExecutor(listener, executor); + } + } + + @Override + public void close() { + flush(); + + for (int i = 0; i < mReaders.length; i++) { + mReaders[i].close(); + } + } + + @Override + protected void finalize() { + close(); + } + + /** + * Flush pending images from all internal ImageReaders + * + * <p>Acquire and close pending images from all internal ImageReaders. This has the same + * effect as calling acquireLatestImage() on all internal ImageReaders, and closing all + * latest images.</p> + */ + public void flush() { + flushOther(null); + } + + /** + * Flush pending images from other internal ImageReaders + * + * <p>Acquire and close pending images from all internal ImageReaders except for the + * one specified.</p> + * + * @param reader The ImageReader object that won't be flushed. + * + * @hide + */ + public void flushOther(ImageReader reader) { + for (int i = 0; i < mReaders.length; i++) { + if (reader != null && reader == mReaders[i]) { + continue; + } + + while (true) { + Image image = mReaders[i].acquireNextImageNoThrowISE(); + if (image == null) { + break; + } else { + image.close(); + } + } + } + } + + /** + * Get the internal ImageReader objects + * + * @hide + */ + public @NonNull ImageReader[] getReaders() { + return mReaders; + } + + /** + * Get the surface that is used as a target for {@link CaptureRequest} + * + * <p>The application must use the surface returned by this function as a target for + * {@link CaptureRequest}. The camera device makes the decision on which internal + * {@code ImageReader} will receive the output image.</p> + * + * <p>Please note that holding on to the Surface objects returned by this method is not enough + * to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a + * Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the + * MultiResolutionImageReader that provides it.</p> + * + * @return a {@link Surface} to use as the target for a capture request. + */ + public @NonNull Surface getSurface() { + //TODO: Pick the surface from the reader for default mode stream. + return mReaders[0].getSurface(); + } + + /** + * Get the MultiResolutionStreamInfo describing the ImageReader an image originates from + * + *<p>An image from a {@code MultiResolutionImageReader} is produced from one of the underlying + *{@code ImageReader}s. This function returns the {@link MultiResolutionStreamInfo} to describe + *the property for that {@code ImageReader}, such as width, height, and physical camera Id.</p> + * + * @param reader An internal ImageReader within {@code MultiResolutionImageReader}. + * + * @return The stream info describing the internal {@code ImageReader}. + */ + public @NonNull MultiResolutionStreamInfo getStreamInfoForImageReader( + @NonNull ImageReader reader) { + for (int i = 0; i < mReaders.length; i++) { + if (reader == mReaders[i]) { + return mStreamInfo[i]; + } + } + + throw new IllegalArgumentException("ImageReader doesn't belong to this multi-resolution " + + "imagereader"); + } + + // mReaders and mStreamInfo has the same length, and their entries are 1:1 mapped. + private final ImageReader[] mReaders; + private final MultiResolutionStreamInfo[] mStreamInfo; + + private final int mFormat; + private final int mMaxImages; +} diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index da65f71ce02c..df8eeccbe800 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -61,8 +61,8 @@ public final class TotalCaptureResult extends CaptureResult { private final List<CaptureResult> mPartialResults; private final int mSessionId; - // The map between physical camera id and capture result - private final HashMap<String, CaptureResult> mPhysicalCaptureResults; + // The map between physical camera ids and their total capture result + private final HashMap<String, TotalCaptureResult> mPhysicalCaptureResults; /** * Takes ownership of the passed-in camera metadata and the partial results @@ -83,10 +83,11 @@ public final class TotalCaptureResult extends CaptureResult { mSessionId = sessionId; - mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); + mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>(); for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) { - CaptureResult physicalResult = new CaptureResult(onePhysicalResult.getCameraId(), - onePhysicalResult.getCameraMetadata(), parent, extras); + TotalCaptureResult physicalResult = new TotalCaptureResult( + onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(), + parent, extras, /*partials*/null, sessionId, new PhysicalCaptureResultInfo[0]); mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(), physicalResult); } @@ -103,7 +104,7 @@ public final class TotalCaptureResult extends CaptureResult { mPartialResults = new ArrayList<>(); mSessionId = CameraCaptureSession.SESSION_ID_NONE; - mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); + mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>(); } /** @@ -146,8 +147,37 @@ public final class TotalCaptureResult extends CaptureResult { * cameras. Otherwise, an empty map is returned.</p> * @return unmodifiable map between physical camera ids and their capture result metadata + * + * @deprecated + * <p>Please use {@link #getPhysicalCameraTotalResults() instead to get the + * physical cameras' {@code TotalCaptureResult}.</p> */ public Map<String, CaptureResult> getPhysicalCameraResults() { return Collections.unmodifiableMap(mPhysicalCaptureResults); } + + /** + * Get the map between physical camera ids and their total capture result metadata + * + * <p>This function can be called for logical multi-camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability.</p> + * + * <p>If one or more streams from the underlying physical cameras were requested by the + * corresponding capture request, this function returns the total result metadata for those + * physical cameras. Otherwise, an empty map is returned.</p> + * + * <p>This function replaces the deprecated {@link #getPhysicalCameraResults}, and its return + * value is a map of TotalCaptureResult rather than CaptureResult. </p> + * + * <p>To reprocess an image from a physical camera stream, typically returned from a + * {@link MultiResolutionImageReader}, the application must look up this map to get the {@link + * TotalCaptureResult} from the physical camera and pass it to {@link + * CameraDevice#createReprocessCaptureRequest}.</p> + * + * @return unmodifiable map between physical camera ids and their total capture result metadata + */ + @NonNull + public Map<String, TotalCaptureResult> getPhysicalCameraTotalResults() { + return Collections.unmodifiableMap(mPhysicalCaptureResults); + } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index ce3c81a2bfd6..4defd23231b8 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -36,6 +36,8 @@ import android.hardware.camera2.ICameraOfflineSession; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.InputConfiguration; +import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap; +import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; @@ -468,7 +470,8 @@ public class CameraDeviceImpl extends CameraDevice } if (inputConfig != null) { int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), - inputConfig.getHeight(), inputConfig.getFormat()); + inputConfig.getHeight(), inputConfig.getFormat(), + inputConfig.isMultiResolution()); mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( streamId, inputConfig); } @@ -1355,7 +1358,42 @@ public class CameraDeviceImpl extends CameraDevice } private void checkInputConfiguration(InputConfiguration inputConfig) { - if (inputConfig != null) { + if (inputConfig == null) { + return; + } + + if (inputConfig.isMultiResolution()) { + MultiResolutionStreamConfigurationMap configMap = mCharacteristics.get( + CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP); + + int[] inputFormats = configMap.getInputFormats(); + boolean validFormat = false; + for (int format : inputFormats) { + if (format == inputConfig.getFormat()) { + validFormat = true; + } + } + + if (validFormat == false) { + throw new IllegalArgumentException("multi-resolution input format " + + inputConfig.getFormat() + " is not valid"); + } + + boolean validSize = false; + Collection<MultiResolutionStreamInfo> inputStreamInfo = + configMap.getInputInfo(inputConfig.getFormat()); + for (MultiResolutionStreamInfo info : inputStreamInfo) { + if (inputConfig.getWidth() == info.getWidth() && + inputConfig.getHeight() == info.getHeight()) { + validSize = true; + } + } + + if (validSize == false) { + throw new IllegalArgumentException("Multi-resolution input size " + + inputConfig.getWidth() + "x" + inputConfig.getHeight() + " is not valid"); + } + } else { StreamConfigurationMap configMap = mCharacteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index e9bae0b621ce..0cdf744ecd68 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -16,6 +16,7 @@ package android.hardware.camera2.impl; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.ImageFormat; import android.graphics.Point; @@ -53,6 +54,7 @@ import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; import android.hardware.camera2.params.MandatoryStreamCombination; +import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap; import android.hardware.camera2.params.OisSample; import android.hardware.camera2.params.RecommendedStreamConfiguration; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; @@ -61,6 +63,7 @@ import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.TonemapCurve; +import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.TypeReference; import android.location.Location; import android.location.LocationManager; @@ -79,9 +82,14 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Implementation of camera metadata marshal/unmarshal across Binder to @@ -747,6 +755,15 @@ public class CameraMetadataNative implements Parcelable { return (T) metadata.getExtendedSceneModeCapabilities(); } }); + sGetCommandMap.put( + CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMultiResolutionStreamConfigurationMap(); + } + }); } private int[] getAvailableFormats() { @@ -1688,6 +1705,7 @@ public class CameraMetadataNative implements Parcelable { private boolean mHasMandatoryConcurrentStreams = false; private Size mDisplaySize = new Size(0, 0); private long mBufferSize = 0; + private MultiResolutionStreamConfigurationMap mMultiResolutionStreamConfigurationMap = null; /** * Set the current camera Id. @@ -1723,6 +1741,30 @@ public class CameraMetadataNative implements Parcelable { mDisplaySize = displaySize; } + /** + * Set the multi-resolution stream configuration map. + * + * @param multiResolutionMap The multi-resolution stream configuration map. + * + * @hide + */ + public void setMultiResolutionStreamConfigurationMap( + @NonNull Map<String, StreamConfiguration[]> multiResolutionMap) { + mMultiResolutionStreamConfigurationMap = + new MultiResolutionStreamConfigurationMap(multiResolutionMap); + } + + /** + * Get the multi-resolution stream configuration map. + * + * @return The multi-resolution stream configuration map. + * + * @hide + */ + public MultiResolutionStreamConfigurationMap getMultiResolutionStreamConfigurationMap() { + return mMultiResolutionStreamConfigurationMap; + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private long mMetadataPtr; // native std::shared_ptr<CameraMetadata>* @@ -1777,6 +1819,7 @@ public class CameraMetadataNative implements Parcelable { mCameraId = other.mCameraId; mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams; mDisplaySize = other.mDisplaySize; + mMultiResolutionStreamConfigurationMap = other.mMultiResolutionStreamConfigurationMap; updateNativeAllocation(); other.updateNativeAllocation(); } @@ -1980,6 +2023,39 @@ public class CameraMetadataNative implements Parcelable { return true; } + /** + * Return the set of physical camera ids that this logical {@link CameraDevice} is made + * up of. + * + * If the camera device isn't a logical camera, return an empty set. + * + * @hide + */ + public Set<String> getPhysicalCameraIds() { + int[] availableCapabilities = get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); + if (availableCapabilities == null) { + throw new AssertionError("android.request.availableCapabilities must be non-null " + + "in the characteristics"); + } + + if (!ArrayUtils.contains(availableCapabilities, + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { + return Collections.emptySet(); + } + byte[] physicalCamIds = get(CameraCharacteristics.LOGICAL_MULTI_CAMERA_PHYSICAL_IDS); + + String physicalCamIdString = null; + try { + physicalCamIdString = new String(physicalCamIds, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + throw new AssertionError("android.logicalCam.physicalIds must be UTF-8 string"); + } + String[] physicalCameraIdArray = physicalCamIdString.split("\0"); + + return Collections.unmodifiableSet( + new HashSet<String>(Arrays.asList(physicalCameraIdArray))); + } + static { registerAllMarshalers(); } diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index ba4395f70214..b6b1968bfcdd 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -140,9 +140,10 @@ public class ICameraDeviceUserWrapper { } } - public int createInputStream(int width, int height, int format) throws CameraAccessException { + public int createInputStream(int width, int height, int format, boolean isMultiResolution) + throws CameraAccessException { try { - return mRemoteDevice.createInputStream(width, height, format); + return mRemoteDevice.createInputStream(width, height, format, isMultiResolution); } catch (Throwable t) { CameraManager.throwAsPublicException(t); throw new UnsupportedOperationException("Unexpected exception", t); diff --git a/core/java/android/hardware/camera2/params/InputConfiguration.java b/core/java/android/hardware/camera2/params/InputConfiguration.java index 0a50f974aca8..d63683feed9b 100644 --- a/core/java/android/hardware/camera2/params/InputConfiguration.java +++ b/core/java/android/hardware/camera2/params/InputConfiguration.java @@ -16,9 +16,17 @@ package android.hardware.camera2.params; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.ImageFormat.Format; +import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.utils.HashCodeHelpers; +import java.util.Collection; +import java.util.List; + +import static com.android.internal.util.Preconditions.*; + /** * Immutable class to store an input configuration that is used to create a reprocessable capture * session. @@ -31,11 +39,12 @@ public final class InputConfiguration { private final int mWidth; private final int mHeight; private final int mFormat; + private final boolean mIsMultiResolution; /** * Create an input configration with the width, height, and user-defined format. * - * <p>Images of an user-defined format are accessible by applications. Use + * <p>Images of a user-defined format are accessible by applications. Use * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP} * to query supported input formats</p> * @@ -51,6 +60,52 @@ public final class InputConfiguration { mWidth = width; mHeight = height; mFormat = format; + mIsMultiResolution = false; + } + + /** + * Create an input configration with the format and a list of multi-resolution input stream + * info. + * + * <p>Use {@link + * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP} + * to query supported multi-resolution input formats.</p> + * + * <p>To do reprocessing with variable resolution input, the application calls + * {@link android.media.ImageWriter#queueInputImage ImageWriter.queueInputImage} + * using an image from an {@link android.media.ImageReader ImageReader} or {@link + * android.hardware.camera2.MultiResolutionImageReader MultiResolutionImageReader}. See + * {@link android.hardware.camera2.CameraDevice#createReprocessCaptureRequest} for more + * details on camera reprocessing. + * </p> + * + * @param multiResolutionInputs A group of multi-resolution input info for the specified format. + * @param format Format of the input buffers. One of ImageFormat or PixelFormat constants. + * + * @see android.graphics.ImageFormat + * @see android.graphics.PixelFormat + * @see + * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP + */ + public InputConfiguration(@NonNull Collection<MultiResolutionStreamInfo> multiResolutionInputs, + @Format int format) { + checkCollectionNotEmpty(multiResolutionInputs, "Input multi-resolution stream info"); + //TODO: Pick the default mode stream info for ultra-high resolution sensor camera + MultiResolutionStreamInfo info = multiResolutionInputs.iterator().next(); + mWidth = info.getWidth(); + mHeight = info.getHeight(); + mFormat = format; + mIsMultiResolution = true; + } + + /** + * @hide + */ + public InputConfiguration(int width, int height, int format, boolean isMultiResolution) { + mWidth = width; + mHeight = height; + mFormat = format; + mIsMultiResolution = isMultiResolution; } /** @@ -81,6 +136,18 @@ public final class InputConfiguration { } /** + * Whether this input configuration is of multi-resolution. + * + * <p>An multi-resolution InputConfiguration means that the reprocessing session created from it + * allows input images of different sizes.</p> + * + * @return this input configuration is multi-resolution or not. + */ + public boolean isMultiResolution() { + return mIsMultiResolution; + } + + /** * Check if this InputConfiguration is equal to another InputConfiguration. * * <p>Two input configurations are equal if and only if they have the same widths, heights, and @@ -100,7 +167,8 @@ public final class InputConfiguration { if (otherInputConfig.getWidth() == mWidth && otherInputConfig.getHeight() == mHeight && - otherInputConfig.getFormat() == mFormat) { + otherInputConfig.getFormat() == mFormat && + otherInputConfig.isMultiResolution() == mIsMultiResolution) { return true; } return false; @@ -111,19 +179,21 @@ public final class InputConfiguration { */ @Override public int hashCode() { - return HashCodeHelpers.hashCode(mWidth, mHeight, mFormat); + return HashCodeHelpers.hashCode(mWidth, mHeight, mFormat, mIsMultiResolution ? 1 : 0); } /** * Return this {@link InputConfiguration} as a string representation. * - * <p> {@code "InputConfiguration(w:%d, h:%d, format:%d)"}, where {@code %d} represents - * the width, height, and format, respectively.</p> + * <p> {@code "InputConfiguration(w:%d, h:%d, format:%d, isMultiResolution:%d)"}, + * where {@code %d} represents the width, height, format, and multi-resolution flag + * respectively.</p> * * @return string representation of {@link InputConfiguration} */ @Override public String toString() { - return String.format("InputConfiguration(w:%d, h:%d, format:%d)", mWidth, mHeight, mFormat); + return String.format("InputConfiguration(w:%d, h:%d, format:%d, isMultiResolution %b)", + mWidth, mHeight, mFormat, mIsMultiResolution); } } diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 776d155e5b3e..8a0172ee8018 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -1297,19 +1297,6 @@ public final class MandatoryStreamCombination { } /** - * Size comparison method used by size comparators. - */ - private static int compareSizes(int widthA, int heightA, int widthB, int heightB) { - long left = widthA * (long) heightA; - long right = widthB * (long) heightB; - if (left == right) { - left = widthA; - right = widthB; - } - return (left < right) ? -1 : (left > right ? 1 : 0); - } - - /** * Size comparator that compares the number of pixels it covers. * * <p>If two the areas of two sizes are same, compare the widths.</p> @@ -1317,8 +1304,8 @@ public final class MandatoryStreamCombination { public static class SizeComparator implements Comparator<Size> { @Override public int compare(@NonNull Size lhs, @NonNull Size rhs) { - return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), - rhs.getHeight()); + return StreamConfigurationMap.compareSizes(lhs.getWidth(), lhs.getHeight(), + rhs.getWidth(), rhs.getHeight()); } } diff --git a/core/java/android/hardware/camera2/params/MultiResolutionStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/MultiResolutionStreamConfigurationMap.java new file mode 100644 index 000000000000..1b368fb8c010 --- /dev/null +++ b/core/java/android/hardware/camera2/params/MultiResolutionStreamConfigurationMap.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.camera2.params; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import android.graphics.ImageFormat; +import android.graphics.ImageFormat.Format; +import android.graphics.PixelFormat; +import android.hardware.camera2.params.MultiResolutionStreamInfo; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.utils.HashCodeHelpers; + +import android.util.Size; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.Set; + +import static com.android.internal.util.Preconditions.*; + +/** + * Immutable class to store the information of the multi-resolution streams supported by + * the camera device. + * + * <p>For a {@link + * android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA + * logical multi-camera} or an ultra high resolution sensor camera, the maximum resolution of images + * produced by the camera device may be variable. For example, for a logical multi-camera, depending + * on factors such as current zoom ratio, the camera device may be backed by different physical + * cameras. If the physical cameras are of different resolutions, the application may intend to + * consume the variable full resolution images from the physical cameras. For an ultra high + * resolution sensor camera, the same use case exists where depending on lighting conditions, the + * camera device may deem it better to run in default mode and maximum resolution mode. + * </p> + * + * <p>For the use cases described above, multi-resolution output streams can be used by + * {@link android.hardware.camera2.MultiResolutionImageReader} to allow the + * camera device to output variable size maximum-resolution images.</p> + * + * <p>Similarly, multi-resolution input streams can be used for reprocessing of variable size + * images. In order to reprocess input images of different sizes, the {@link InputConfiguration} + * used for creating reprocessable session can be initialized using the group of input stream + * configurations returned by {@link #getInputInfo}.</p> + */ +public final class MultiResolutionStreamConfigurationMap { + /** + * Create a new {@link MultiResolutionStreamConfigurationMap}. + * + * @param configurations a non-{@code null} array of multi-resolution stream + * configurations supported by this camera device + * @hide + */ + public MultiResolutionStreamConfigurationMap( + @NonNull Map<String, StreamConfiguration[]> configurations) { + checkNotNull(configurations, "multi-resolution configurations must not be null"); + if (configurations.size() == 0) { + throw new IllegalArgumentException("multi-resolution configurations must not be empty"); + } + + mConfigurations = configurations; + + // For each multi-resolution stream configuration, track how many formats and sizes there + // are available to configure + for (Map.Entry<String, StreamConfiguration[]> entry : + mConfigurations.entrySet()) { + String cameraId = entry.getKey(); + StreamConfiguration[] configs = entry.getValue(); + + for (int i = 0; i < configs.length; i++) { + StreamConfiguration config = configs[i]; + int format = config.getFormat(); + + MultiResolutionStreamInfo multiResolutionStreamInfo = new MultiResolutionStreamInfo( + config.getWidth(), config.getHeight(), cameraId); + Map<Integer, List<MultiResolutionStreamInfo>> destMap; + if (config.isInput()) { + destMap = mMultiResolutionInputConfigs; + } else { + destMap = mMultiResolutionOutputConfigs; + } + + if (!destMap.containsKey(format)) { + List<MultiResolutionStreamInfo> multiResolutionStreamInfoList = + new ArrayList<MultiResolutionStreamInfo>(); + destMap.put(format, multiResolutionStreamInfoList); + } + destMap.get(format).add(multiResolutionStreamInfo); + } + } + } + + /** + * Size comparator that compares the number of pixels two MultiResolutionStreamInfo size covers. + * + * <p>If two the areas of two sizes are same, compare the widths.</p> + * + * @hide + */ + public static class SizeComparator implements Comparator<MultiResolutionStreamInfo> { + @Override + public int compare(@NonNull MultiResolutionStreamInfo lhs, + @NonNull MultiResolutionStreamInfo rhs) { + return StreamConfigurationMap.compareSizes( + lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight()); + } + } + + /** + * Get the output formats in this multi-resolution stream configuration. + * + * <p>A logical multi-camera or an ultra high resolution sensor camera may support + * {@link android.hardware.camera2.MultiResolutionImageReader} to dynamically output maximum + * resolutions of different sizes (when switching between physical cameras, or between different + * modes of an ultra high resolution sensor camera). This function returns the formats + * supported for such case.</p> + * + * <p>All image formats returned by this function will be defined in either {@link ImageFormat} + * or in {@link PixelFormat} (and there is no possibility of collision).</p> + * + * @return an array of integer format, or empty array if multi-resolution output is not + * supported + * + * @see ImageFormat + * @see PixelFormat + * @see android.hardware.camera2.MultiResolutionImageReader + */ + public @NonNull @Format int[] getOutputFormats() { + return getPublicImageFormats(/*output*/true); + } + + /** + * Get the input formats in this multi-resolution stream configuration. + * + * <p>A logical multi-camera or ultra high resolution sensor camera may support reprocessing + * images of different resolutions when switching between physical cameras, or between + * different modes of the ultra high resolution sensor camera. This function returns the + * formats supported for such case.</p> + * + * <p>The supported output format for an input format can be queried by calling the camera + * device's {@link StreamConfigurationMap#getValidOutputFormatsForInput}.</p> + * + * <p>All image formats returned by this function will be defined in either {@link ImageFormat} + * or in {@link PixelFormat} (and there is no possibility of collision).</p> + * + * @return an array of integer format, or empty array if no multi-resolution reprocessing is + * supported + * + * @see ImageFormat + * @see PixelFormat + */ + public @NonNull @Format int[] getInputFormats() { + return getPublicImageFormats(/*output*/false); + } + + // Get the list of publicly visible multi-resolution input/output stream formats + private int[] getPublicImageFormats(boolean output) { + Map<Integer, List<MultiResolutionStreamInfo>> multiResolutionConfigs = + output ? mMultiResolutionOutputConfigs : mMultiResolutionInputConfigs; + int formatCount = multiResolutionConfigs.size(); + + int[] formats = new int[formatCount]; + int i = 0; + for (Integer format : multiResolutionConfigs.keySet()) { + formats[i++] = StreamConfigurationMap.imageFormatToPublic(format); + } + + return formats; + } + + /** + * Get a group of {@code MultiResolutionStreamInfo} with the requested output image + * {@code format} + * + * <p>The {@code format} should be a supported format (one of the formats returned by + * {@link #getOutputFormats}).</p> + * + * @param format an image format from {@link ImageFormat} or {@link PixelFormat} + * @return + * a group of supported {@link MultiResolutionStreamInfo}. If the {@code format} is not + * a supported multi-resolution output, an empty group is returned. + * + * @see ImageFormat + * @see PixelFormat + * @see #getOutputFormats + */ + public @NonNull Collection<MultiResolutionStreamInfo> getOutputInfo(@Format int format) { + return getInfo(format, /*false*/ true); + } + + /** + * Get a group of {@code MultiResolutionStreamInfo} with the requested input image {@code format} + * + * <p>The {@code format} should be a supported format (one of the formats returned by + * {@link #getInputFormats}).</p> + * + * @param format an image format from {@link ImageFormat} or {@link PixelFormat} + * @return + * a group of supported {@link MultiResolutionStreamInfo}. If the {@code format} is not + * a supported multi-resolution input, an empty group is returned. + * + * @see ImageFormat + * @see PixelFormat + * @see #getInputFormats + */ + public @NonNull Collection<MultiResolutionStreamInfo> getInputInfo(@Format int format) { + return getInfo(format, /*false*/ false); + } + + // Get multi-resolution stream info for a particular format + private @NonNull Collection<MultiResolutionStreamInfo> getInfo(int format, boolean output) { + int internalFormat = StreamConfigurationMap.imageFormatToInternal(format); + Map<Integer, List<MultiResolutionStreamInfo>> multiResolutionConfigs = + output ? mMultiResolutionOutputConfigs : mMultiResolutionInputConfigs; + if (multiResolutionConfigs.containsKey(internalFormat)) { + return Collections.unmodifiableCollection(multiResolutionConfigs.get(internalFormat)); + } else { + return Collections.emptyList(); + } + } + + private void appendConfigurationsString(StringBuilder sb, boolean output) { + sb.append(output ? "Outputs(" : "Inputs("); + int[] formats = getPublicImageFormats(output); + if (formats != null) { + for (int format : formats) { + Collection<MultiResolutionStreamInfo> streamInfoList = + getInfo(format, output); + sb.append("[" + StreamConfigurationMap.formatToString(format) + ":"); + for (MultiResolutionStreamInfo streamInfo : streamInfoList) { + sb.append(String.format("[w:%d, h:%d, id:%s], ", + streamInfo.getWidth(), streamInfo.getHeight(), + streamInfo.getPhysicalCameraId())); + } + // Remove the pending ", " + if (sb.charAt(sb.length() - 1) == ' ') { + sb.delete(sb.length() - 2, sb.length()); + } + sb.append("]"); + } + } + sb.append(")"); + } + + /** + * Check if this {@link MultiResolutionStreamConfigurationMap} is equal to another + * {@link MultiResolutionStreamConfigurationMap}. + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof MultiResolutionStreamConfigurationMap) { + final MultiResolutionStreamConfigurationMap other = + (MultiResolutionStreamConfigurationMap) obj; + if (!mConfigurations.keySet().equals(other.mConfigurations.keySet())) { + return false; + } + + for (String id : mConfigurations.keySet()) { + if (!Arrays.equals(mConfigurations.get(id), other.mConfigurations.get(id))) { + return false; + } + } + + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCodeGeneric( + mConfigurations, mMultiResolutionOutputConfigs, mMultiResolutionInputConfigs); + } + + /** + * Return this {@link MultiResolutionStreamConfigurationMap} as a string representation. + * + * <p>{@code "MultiResolutionStreamConfigurationMap(Outputs([format1: [w:%d, h:%d, id:%s], ... + * ... [w:%d, h:%d, id:%s]), [format2: [w:%d, h:%d, id:%s], ... [w:%d, h:%d, id:%s]], ...), + * Inputs([format1: [w:%d, h:%d, id:%s], ... [w:%d, h:%d, id:%s], ...).</p> + * + * @return string representation of {@link MultiResolutionStreamConfigurationMap} + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("MultiResolutionStreamConfigurationMap("); + appendConfigurationsString(sb, /*output*/ true); + sb.append(","); + appendConfigurationsString(sb, /*output*/ false); + sb.append(")"); + + return sb.toString(); + } + + + private final Map<String, StreamConfiguration[]> mConfigurations; + + /** Format -> list of MultiResolutionStreamInfo used to create MultiResolutionImageReader */ + private final Map<Integer, List<MultiResolutionStreamInfo>> mMultiResolutionOutputConfigs + = new HashMap<Integer, List<MultiResolutionStreamInfo>>(); + /** Format -> list of MultiResolutionStreamInfo used for multi-resolution reprocessing */ + private final Map<Integer, List<MultiResolutionStreamInfo>> mMultiResolutionInputConfigs + = new HashMap<Integer, List<MultiResolutionStreamInfo>>(); +} diff --git a/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java new file mode 100644 index 000000000000..aa1d1d4aaa18 --- /dev/null +++ b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.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 android.hardware.camera2.params; + +import android.annotation.NonNull; + +import java.util.Objects; + +/** + * A utility class describing the properties of one stream of fixed-size image buffers + * backing a multi-resolution image stream. + * + * <p>A group of {@link MultiResolutionStreamInfo} are used to describe the properties of a + * multi-resolution image stream for a particular format. The + * {@link android.hardware.camera2.MultiResolutionImageReader} class represents a + * multi-resolution output stream, and is constructed using a group of + * {@link MultiResolutionStreamInfo}. A group of {@link MultiResolutionStreamInfo} can also be used + * to create a multi-resolution reprocessable camera capture session. See + * {@link android.hardware.camera2.params.InputConfiguration} for details.</p> + * + * @see InputConfiguration + * @see android.hardware.camera2.MultiResolutionImageReader + */ +public class MultiResolutionStreamInfo { + private int mStreamWidth; + private int mStreamHeight; + private String mPhysicalCameraId; + + /** + * Create a new {@link MultiResolutionStreamInfo}. + * + * <p>This class creates a {@link MultiResolutionStreamInfo} using image width, image height, + * and the physical camera Id images originate from.</p> + * + * <p>Normally applications do not need to create these directly. Use {@link + * MultiResolutionStreamConfigurationMap#getOutputInfo} or {@link + * MultiResolutionStreamConfigurationMap#getInputInfo} to obtain them for a particular format + * instead.</p> + */ + public MultiResolutionStreamInfo(int streamWidth, int streamHeight, + @NonNull String physicalCameraId) { + mStreamWidth = streamWidth; + mStreamHeight = streamHeight; + mPhysicalCameraId = physicalCameraId; + } + + /** + * The width of this particular image buffer stream in pixels. + */ + public int getWidth() { + return mStreamWidth; + } + + /** + * The height of this particular image buffer stream in pixels. + */ + public int getHeight() { + return mStreamHeight; + } + + /** + * The physical camera Id of this particular image buffer stream. + */ + public @NonNull String getPhysicalCameraId() { + return mPhysicalCameraId; + } + + /** + * Check if this {@link MultiResolutionStreamInfo} is equal to another + * {@link MultiResolutionStreamInfo}. + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof MultiResolutionStreamInfo) { + final MultiResolutionStreamInfo other = (MultiResolutionStreamInfo) obj; + return mStreamWidth == other.mStreamWidth && + mStreamHeight == other.mStreamHeight && + mPhysicalCameraId.equals(other.mPhysicalCameraId); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash( + mStreamWidth, mStreamHeight, mPhysicalCameraId); + } +} diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index a20a1bf194ea..e31bd601fc03 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -24,9 +24,13 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.graphics.ImageFormat; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.MultiResolutionImageReader; +import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; +import android.media.ImageReader; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -34,6 +38,7 @@ import android.util.Size; import android.view.Surface; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -81,6 +86,13 @@ import java.util.Objects; * {@link CameraCaptureSession#updateOutputConfiguration} can be called after the configuration * finalize method returns without exceptions.</li> * + * <li>If the camera device supports multi-resolution output streams, {@link + * CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP} will contain the + * formats and their corresponding stream info. The application can use an OutputConfiguration + * created with the multi-resolution stream info queried from {@link + * MultiResolutionStreamConfigurationMap#getOutputInfo} and + * {@link android.hardware.camera2.MultiResolutionImageReader} to capture variable size images. + * * </ul> * * <p> As of {@link android.os.Build.VERSION_CODES#P Android P}, all formats except @@ -88,6 +100,7 @@ import java.util.Objects; * device support. On prior API levels, only {@link ImageFormat#PRIVATE} format may be used.</p> * * @see CameraDevice#createCaptureSessionByOutputConfigurations + * @see CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP * */ public final class OutputConfiguration implements Parcelable { @@ -206,6 +219,33 @@ public final class OutputConfiguration implements Parcelable { } /** + * Set the multi-resolution output flag. + * + * <p>Specify that this OutputConfiguration is part of a multi-resolution output stream group + * used by {@link android.hardware.camera2.MultiResolutionImageReader}.</p> + * + * <p>This function must only be called for an OutputConfiguration with a non-negative + * group ID. And all OutputConfigurations of a MultiResolutionImageReader will have the same + * group ID and have this flag set.</p> + * + * @throws IllegalStateException If surface sharing is enabled via {@link #enableSurfaceSharing} + * call, or no non-negative group ID has been set. + * @hide + */ + void setMultiResolutionOutput() { + if (mIsShared) { + throw new IllegalStateException("Multi-resolution output flag must not be set for " + + "configuration with surface sharing"); + } + if (mSurfaceGroupId == SURFACE_GROUP_ID_NONE) { + throw new IllegalStateException("Multi-resolution output flag should only be set for " + + "surface with non-negative group ID"); + } + + mIsMultiResolution = true; + } + + /** * Create a new {@link OutputConfiguration} instance. * * <p>This constructor takes an argument for desired camera rotation</p> @@ -265,6 +305,45 @@ public final class OutputConfiguration implements Parcelable { mIsDeferredConfig = false; mIsShared = false; mPhysicalCameraId = null; + mIsMultiResolution = false; + } + + /** + * Create a list of {@link OutputConfiguration} instances for the outputs used by a + * {@link android.hardware.camera2.MultiResolutionImageReader}. + * + * <p>This constructor takes an argument for a + * {@link android.hardware.camera2.MultiResolutionImageReader}.</p> + * + * @param multiResolutionImageReader + * The multi-resolution image reader object. + */ + public static @NonNull Collection<OutputConfiguration> createInstancesForMultiResolutionOutput( + @NonNull MultiResolutionImageReader multiResolutionImageReader) { + checkNotNull(multiResolutionImageReader, "Multi-resolution image reader must not be null"); + + int groupId = MULTI_RESOLUTION_GROUP_ID_COUNTER; + MULTI_RESOLUTION_GROUP_ID_COUNTER++; + // Skip in case the group id counter overflows to -1, the invalid value. + if (MULTI_RESOLUTION_GROUP_ID_COUNTER == -1) { + MULTI_RESOLUTION_GROUP_ID_COUNTER++; + } + + ImageReader[] imageReaders = multiResolutionImageReader.getReaders(); + ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>(); + for (int i = 0; i < imageReaders.length; i++) { + MultiResolutionStreamInfo streamInfo = + multiResolutionImageReader.getStreamInfoForImageReader(imageReaders[i]); + + OutputConfiguration config = new OutputConfiguration( + groupId, imageReaders[i].getSurface()); + config.setPhysicalCameraId(streamInfo.getPhysicalCameraId()); + config.setMultiResolutionOutput(); + configs.add(config); + // TODO: Set sensor pixel mode for ultra high resolution sensor camera. + } + + return configs; } /** @@ -319,6 +398,7 @@ public final class OutputConfiguration implements Parcelable { mIsDeferredConfig = true; mIsShared = false; mPhysicalCameraId = null; + mIsMultiResolution = false; } /** @@ -355,8 +435,18 @@ public final class OutputConfiguration implements Parcelable { * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration. * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView, * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p> + * + * <p>This function must not be called from OuptutConfigurations created by {@link + * #createInstancesForMultiResolutionOutput}.</p> + * + * @throws IllegalStateException If this OutputConfiguration is created via {@link + * #createInstancesForMultiResolutionOutput} to back a MultiResolutionImageReader. */ public void enableSurfaceSharing() { + if (mIsMultiResolution) { + throw new IllegalStateException("Cannot enable surface sharing on " + + "multi-resolution output configurations"); + } mIsShared = true; } @@ -368,8 +458,7 @@ public final class OutputConfiguration implements Parcelable { * This call achieves it by mapping the OutputConfiguration to the physical camera id.</p> * * <p>The valid physical camera ids can be queried by {@link - * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}. - * </p> + * CameraCharacteristics#getPhysicalCameraIds}.</p> * * <p>Passing in a null physicalCameraId means that the OutputConfiguration is for a logical * stream.</p> @@ -380,8 +469,16 @@ public final class OutputConfiguration implements Parcelable { * after {@link CameraDevice#createCaptureSessionByOutputConfigurations} or {@link * CameraDevice#createReprocessableCaptureSessionByConfigurations} has no effect.</p> * - * <p>The surface belonging to a physical camera OutputConfiguration must not be used as input - * or output of a reprocessing request. </p> + * <p>As of {@link android.os.Build.VERSION_CODES#S Android 12}, an image buffer from a + * physical camera stream can be used for reprocessing to logical camera streams and streams + * from the same physical camera if the camera device supports multi-resolution input and output + * streams. See {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP} + * for details. The behaviors of reprocessing from a non-physical camera stream to a physical + * camera stream, and from a physical camera stream to a physical camera stream of different + * physical camera, are device-specific and not guaranteed to be supported.</p> + * + * <p>On prior API levels, the surface belonging to a physical camera OutputConfiguration must + * not be used as input or output of a reprocessing request. </p> */ public void setPhysicalCameraId(@Nullable String physicalCameraId) { mPhysicalCameraId = physicalCameraId; @@ -527,6 +624,7 @@ public final class OutputConfiguration implements Parcelable { this.mIsDeferredConfig = other.mIsDeferredConfig; this.mIsShared = other.mIsShared; this.mPhysicalCameraId = other.mPhysicalCameraId; + this.mIsMultiResolution = other.mIsMultiResolution; } /** @@ -543,6 +641,7 @@ public final class OutputConfiguration implements Parcelable { ArrayList<Surface> surfaces = new ArrayList<Surface>(); source.readTypedList(surfaces, Surface.CREATOR); String physicalCameraId = source.readString(); + boolean isMultiResolutionOutput = source.readInt() == 1; checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); @@ -566,6 +665,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredGenerationId = 0; } mPhysicalCameraId = physicalCameraId; + mIsMultiResolution = isMultiResolutionOutput; } /** @@ -665,6 +765,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mIsShared ? 1 : 0); dest.writeTypedList(mSurfaces); dest.writeString(mPhysicalCameraId); + dest.writeInt(mIsMultiResolution ? 1 : 0); } /** @@ -694,7 +795,8 @@ public final class OutputConfiguration implements Parcelable { mConfiguredFormat != other.mConfiguredFormat || mConfiguredDataspace != other.mConfiguredDataspace || mConfiguredGenerationId != other.mConfiguredGenerationId || - !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId)) + !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) || + mIsMultiResolution != other.mIsMultiResolution) return false; int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size()); @@ -720,17 +822,24 @@ public final class OutputConfiguration implements Parcelable { return HashCodeHelpers.hashCode( mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, - mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), + mIsMultiResolution ? 1 : 0); } return HashCodeHelpers.hashCode( mRotation, mSurfaces.hashCode(), mConfiguredGenerationId, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, - mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), + mIsMultiResolution ? 1 : 0); } private static final String TAG = "OutputConfiguration"; + + // A surfaceGroupId counter used for MultiResolutionImageReader. Its value is + // incremented everytime {@link createInstancesForMultiResolutionOutput} is called. + private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0; + private ArrayList<Surface> mSurfaces; private final int mRotation; private final int mSurfaceGroupId; @@ -749,4 +858,7 @@ public final class OutputConfiguration implements Parcelable { private boolean mIsShared; // The physical camera id that this output configuration is for. private String mPhysicalCameraId; + // Flag indicating if this config is for a multi-resolution output with a + // MultiResolutionImageReader + private boolean mIsMultiResolution; } diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 8fc919f142a2..ea6b92d4f33e 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -130,11 +130,13 @@ public final class SessionConfiguration implements Parcelable { int inputWidth = source.readInt(); int inputHeight = source.readInt(); int inputFormat = source.readInt(); + boolean isInputMultiResolution = source.readBoolean(); ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration>(); source.readTypedList(outConfigs, OutputConfiguration.CREATOR); if ((inputWidth > 0) && (inputHeight > 0) && (inputFormat != -1)) { - mInputConfig = new InputConfiguration(inputWidth, inputHeight, inputFormat); + mInputConfig = new InputConfiguration(inputWidth, inputHeight, + inputFormat, isInputMultiResolution); } mSessionType = sessionType; mOutputConfigurations = outConfigs; @@ -169,10 +171,12 @@ public final class SessionConfiguration implements Parcelable { dest.writeInt(mInputConfig.getWidth()); dest.writeInt(mInputConfig.getHeight()); dest.writeInt(mInputConfig.getFormat()); + dest.writeBoolean(mInputConfig.isMultiResolution()); } else { dest.writeInt(/*inputWidth*/ 0); dest.writeInt(/*inputHeight*/ 0); dest.writeInt(/*inputFormat*/ -1); + dest.writeBoolean(/*isMultiResolution*/ false); } dest.writeTypedList(mOutputConfigurations); } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index 10a814acd70b..a25ae6041d77 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -1575,7 +1575,7 @@ public final class StreamConfigurationMap { return sizes; } - /** Get the list of publically visible output formats; does not include IMPL_DEFINED */ + /** Get the list of publicly visible output formats */ private int[] getPublicFormats(boolean output) { int[] formats = new int[getPublicFormatCount(output)]; @@ -1746,6 +1746,21 @@ public final class StreamConfigurationMap { return sb.toString(); } + /** + * Size comparison method used by size comparators. + * + * @hide + */ + public static int compareSizes(int widthA, int heightA, int widthB, int heightB) { + long left = widthA * (long) heightA; + long right = widthB * (long) heightB; + if (left == right) { + left = widthA; + right = widthB; + } + return (left < right) ? -1 : (left > right ? 1 : 0); + } + private void appendOutputsString(StringBuilder sb) { sb.append("Outputs("); int[] formats = getOutputFormats(); @@ -1843,7 +1858,10 @@ public final class StreamConfigurationMap { sb.append(")"); } - private String formatToString(int format) { + /** + * @hide + */ + public static String formatToString(int format) { switch (format) { case ImageFormat.YV12: return "YV12"; diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index e247df320115..aafa7d520632 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -537,6 +537,26 @@ public final class ColorDisplayManager { } /** + * Returns the minimum allowed brightness reduction strength in percentage when activated. + * + * @hide + */ + public static int getMinimumReduceBrightColorsStrength(Context context) { + return context.getResources() + .getInteger(R.integer.config_reduceBrightColorsStrengthMin); + } + + /** + * Returns the maximum allowed brightness reduction strength in percentage when activated. + * + * @hide + */ + public static int getMaximumReduceBrightColorsStrength(Context context) { + return context.getResources() + .getInteger(R.integer.config_reduceBrightColorsStrengthMax); + } + + /** * Check if the color transforms are color accelerated. Some transforms are experimental only * on non-accelerated platforms due to the performance implications. * diff --git a/core/java/android/hardware/display/DeviceProductInfo.java b/core/java/android/hardware/display/DeviceProductInfo.java index 41126b70c89f..9457d8f1aac4 100644 --- a/core/java/android/hardware/display/DeviceProductInfo.java +++ b/core/java/android/hardware/display/DeviceProductInfo.java @@ -16,40 +16,69 @@ package android.hardware.display; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import java.util.Arrays; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * Product-specific information about the display or the directly connected device on the * display chain. For example, if the display is transitively connected, this field may contain * product information about the intermediate device. - * @hide */ public final class DeviceProductInfo implements Parcelable { + /** @hide */ + @IntDef(prefix = {"CONNECTION_TO_SINK_"}, value = { + CONNECTION_TO_SINK_UNKNOWN, + CONNECTION_TO_SINK_BUILT_IN, + CONNECTION_TO_SINK_DIRECT, + CONNECTION_TO_SINK_TRANSITIVE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConnectionToSinkType { } + + /** The device connection to the display sink is unknown. */ + public static final int CONNECTION_TO_SINK_UNKNOWN = + IDeviceProductInfoConstants.CONNECTION_TO_SINK_UNKNOWN; + + /** The display sink is built-in to the device */ + public static final int CONNECTION_TO_SINK_BUILT_IN = + IDeviceProductInfoConstants.CONNECTION_TO_SINK_BUILT_IN; + + /** The device is directly connected to the display sink. */ + public static final int CONNECTION_TO_SINK_DIRECT = + IDeviceProductInfoConstants.CONNECTION_TO_SINK_DIRECT; + + /** The device is transitively connected to the display sink. */ + public static final int CONNECTION_TO_SINK_TRANSITIVE = + IDeviceProductInfoConstants.CONNECTION_TO_SINK_TRANSITIVE; + private final String mName; private final String mManufacturerPnpId; private final String mProductId; private final Integer mModelYear; private final ManufactureDate mManufactureDate; - private final int[] mRelativeAddress; + private final @ConnectionToSinkType int mConnectionToSinkType; + /** @hide */ public DeviceProductInfo( String name, String manufacturerPnpId, String productId, Integer modelYear, ManufactureDate manufactureDate, - int[] relativeAddress) { + int connectionToSinkType) { this.mName = name; this.mManufacturerPnpId = manufacturerPnpId; this.mProductId = productId; this.mModelYear = modelYear; this.mManufactureDate = manufactureDate; - this.mRelativeAddress = relativeAddress; + this.mConnectionToSinkType = connectionToSinkType; } private DeviceProductInfo(Parcel in) { @@ -58,12 +87,13 @@ public final class DeviceProductInfo implements Parcelable { mProductId = (String) in.readValue(null); mModelYear = (Integer) in.readValue(null); mManufactureDate = (ManufactureDate) in.readValue(null); - mRelativeAddress = in.createIntArray(); + mConnectionToSinkType = in.readInt(); } /** * @return Display name. */ + @Nullable public String getName() { return mName; } @@ -71,6 +101,7 @@ public final class DeviceProductInfo implements Parcelable { /** * @return Manufacturer Plug and Play ID. */ + @NonNull public String getManufacturerPnpId() { return mManufacturerPnpId; } @@ -78,32 +109,58 @@ public final class DeviceProductInfo implements Parcelable { /** * @return Manufacturer product ID. */ + @NonNull public String getProductId() { return mProductId; } /** - * @return Model year of the device. Typically exactly one of model year or - * manufacture date will be present. + * @return Model year of the device. Return -1 if not available. Typically, + * one of model year or manufacture year is available. */ - public Integer getModelYear() { - return mModelYear; + public int getModelYear() { + return mModelYear != null ? mModelYear : -1; + } + + /** + * @return The year of manufacture, or -1 it is not available. Typically, + * one of model year or manufacture year is available. + */ + public int getManufactureYear() { + if (mManufactureDate == null) { + return -1; + } + return mManufactureDate.mYear != null ? mManufactureDate.mYear : -1; + } + + /** + * @return The week of manufacture, or -1 it is not available. Typically, + * not present if model year is available. + */ + public int getManufactureWeek() { + if (mManufactureDate == null) { + return -1; + } + return mManufactureDate.mWeek != null ? mManufactureDate.mWeek : -1; } /** * @return Manufacture date. Typically exactly one of model year or manufacture * date will be present. + * + * @hide */ public ManufactureDate getManufactureDate() { return mManufactureDate; } /** - * @return Relative address in the display network. For example, for HDMI connected devices this - * can be its physical address. Each component of the address is in the range [0, 255]. + * @return How the current device is connected to the display sink. For example, the display + * can be connected immediately to the device or there can be a receiver in between. */ - public int[] getRelativeAddress() { - return mRelativeAddress; + @ConnectionToSinkType + public int getConnectionToSinkType() { + return mConnectionToSinkType; } @Override @@ -119,8 +176,8 @@ public final class DeviceProductInfo implements Parcelable { + mModelYear + ", manufactureDate=" + mManufactureDate - + ", relativeAddress=" - + Arrays.toString(mRelativeAddress) + + ", connectionToSinkType=" + + mConnectionToSinkType + '}'; } @@ -134,16 +191,16 @@ public final class DeviceProductInfo implements Parcelable { && Objects.equals(mProductId, that.mProductId) && Objects.equals(mModelYear, that.mModelYear) && Objects.equals(mManufactureDate, that.mManufactureDate) - && Arrays.equals(mRelativeAddress, that.mRelativeAddress); + && mConnectionToSinkType == that.mConnectionToSinkType; } @Override public int hashCode() { return Objects.hash(mName, mManufacturerPnpId, mProductId, mModelYear, mManufactureDate, - Arrays.hashCode(mRelativeAddress)); + mConnectionToSinkType); } - public static final Creator<DeviceProductInfo> CREATOR = + @NonNull public static final Creator<DeviceProductInfo> CREATOR = new Creator<DeviceProductInfo>() { @Override public DeviceProductInfo createFromParcel(Parcel in) { @@ -162,13 +219,13 @@ public final class DeviceProductInfo implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mName); dest.writeString(mManufacturerPnpId); dest.writeValue(mProductId); dest.writeValue(mModelYear); dest.writeValue(mManufactureDate); - dest.writeIntArray(mRelativeAddress); + dest.writeInt(mConnectionToSinkType); } /** diff --git a/core/java/android/hardware/lights/LightsRequest.java b/core/java/android/hardware/lights/LightsRequest.java index 2626a461aaf5..6fb0eb5df59a 100644 --- a/core/java/android/hardware/lights/LightsRequest.java +++ b/core/java/android/hardware/lights/LightsRequest.java @@ -17,6 +17,7 @@ package android.hardware.lights; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.util.SparseArray; import com.android.internal.util.Preconditions; @@ -94,6 +95,20 @@ public final class LightsRequest { } /** + * Overrides the color and intensity of a given light. + * + * @param light the light to modify + * @param state the desired color and intensity of the light * + * @deprecated Use {@link #addLight(Light, LightState)} instead. + * @hide + */ + @SystemApi + @Deprecated + public @NonNull Builder setLight(@NonNull Light light, @NonNull LightState state) { + return addLight(light, state); + } + + /** * Removes the override for the color and intensity of a given light. * * @param light the light to modify diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 06b5b6745bd1..a5c9a7fafbd8 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -34,10 +34,7 @@ import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; import android.os.ParcelFileDescriptor; import android.os.SharedMemory; -import android.system.ErrnoException; -import java.io.FileDescriptor; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.UUID; @@ -111,13 +108,9 @@ class ConversionUtil { aidlModel.type = apiModel.getType(); aidlModel.uuid = api2aidlUuid(apiModel.getUuid()); aidlModel.vendorUuid = api2aidlUuid(apiModel.getVendorUuid()); - try { - aidlModel.data = ParcelFileDescriptor.dup( - byteArrayToSharedMemory(apiModel.getData(), "SoundTrigger SoundModel")); - } catch (IOException e) { - throw new RuntimeException(e); - } - aidlModel.dataSize = apiModel.getData().length; + byte[] data = apiModel.getData(); + aidlModel.data = byteArrayToSharedMemory(data, "SoundTrigger SoundModel"); + aidlModel.dataSize = data.length; return aidlModel; } @@ -379,7 +372,7 @@ class ConversionUtil { return result; } - private static @Nullable FileDescriptor byteArrayToSharedMemory(byte[] data, String name) { + private static @Nullable ParcelFileDescriptor byteArrayToSharedMemory(byte[] data, String name) { if (data.length == 0) { return null; } @@ -389,8 +382,10 @@ class ConversionUtil { ByteBuffer buffer = shmem.mapReadWrite(); buffer.put(data); shmem.unmap(buffer); - return shmem.getFileDescriptor(); - } catch (ErrnoException e) { + ParcelFileDescriptor fd = shmem.getFdDup(); + shmem.close(); + return fd; + } catch (Exception e) { throw new RuntimeException(e); } } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index b016ed67c4d9..9bf791ba33e0 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -19,8 +19,6 @@ package android.net; import android.net.INetworkPolicyListener; import android.net.Network; import android.net.NetworkPolicy; -import android.net.NetworkQuotaInfo; -import android.net.NetworkState; import android.net.NetworkTemplate; import android.telephony.SubscriptionPlan; @@ -70,9 +68,6 @@ interface INetworkPolicyManager { int getMultipathPreference(in Network network); - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state); - SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage); void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage); String getSubscriptionPlansOwner(int subId); diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 0baf11e850c7..dc3b88a7c3be 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -19,7 +19,7 @@ package android.net; import android.net.DataUsageRequest; import android.net.INetworkStatsSession; import android.net.Network; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; @@ -68,7 +68,7 @@ interface INetworkStatsService { /** Force update of ifaces. */ void forceUpdateIfaces( in Network[] defaultNetworks, - in NetworkState[] networkStates, + in NetworkStateSnapshot[] snapshots, in String activeIface, in UnderlyingNetworkInfo[] underlyingNetworkInfos); /** Force update of statistics. */ diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index 183f500572bd..cc1312bac180 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -24,10 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.content.pm.PackageManager; -import android.os.Process; import android.security.Credentials; -import android.security.KeyStore; -import android.security.keystore.AndroidKeyStoreProvider; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.VpnProfile; @@ -35,7 +32,9 @@ import com.android.internal.net.VpnProfile; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.Key; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; @@ -66,6 +65,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** Prefix for when a Private Key is stored directly in the profile @hide */ public static final String PREFIX_INLINE = "INLINE:"; + private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore"; private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; private static final String EMPTY_CERT = ""; @@ -430,32 +430,31 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return profile; } - /** - * Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance. - * - * <p>Redundant authentication information (not related to profile type) will be discarded. - * - * @hide - */ - @NonNull - public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) - throws IOException, GeneralSecurityException { - return fromVpnProfile(profile, null); + private static PrivateKey getPrivateKeyFromAndroidKeystore(String alias) { + try { + final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER); + keystore.load(null); + final Key key = keystore.getKey(alias, null); + if (!(key instanceof PrivateKey)) { + throw new IllegalStateException( + "Unexpected key type returned from android keystore."); + } + return (PrivateKey) key; + } catch (Exception e) { + throw new IllegalStateException("Failed to load key from android keystore.", e); + } } /** * Builds the Ikev2VpnProfile from the given profile. * * @param profile the source VpnProfile to build from - * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if - * the private key is PEM-encoded into the profile. * @return The IKEv2/IPsec VPN profile * @hide */ @NonNull - public static Ikev2VpnProfile fromVpnProfile( - @NonNull VpnProfile profile, @Nullable KeyStore keyStore) - throws IOException, GeneralSecurityException { + public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) + throws GeneralSecurityException { final Builder builder = new Builder(profile.server, profile.ipsecIdentifier); builder.setProxy(profile.proxy); builder.setAllowedAlgorithms(profile.getAllowedAlgorithms()); @@ -479,12 +478,9 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { case TYPE_IKEV2_IPSEC_RSA: final PrivateKey key; if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) { - Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey"); - final String alias = profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length()); - key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( - keyStore, alias, Process.myUid()); + key = getPrivateKeyFromAndroidKeystore(alias); } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) { key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length())); } else { diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index e89451e4f4ef..8f1e2defd215 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -146,6 +146,25 @@ public final class IpSecAlgorithm implements Parcelable { public static final String AUTH_AES_XCBC = "xcbc(aes)"; /** + * AES-CMAC Authentication/Integrity Algorithm. + * + * <p>Keys for this algorithm must be 128 bits in length. + * + * <p>The only valid truncation length is 96 bits. + * + * <p>This algorithm may be available on the device. Caller MUST check if it is supported before + * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is + * included in the returned algorithm set. The returned algorithm set will not change unless the + * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is + * requested on an unsupported device. + * + * <p>@see {@link #getSupportedAlgorithms()} + */ + // This algorithm may be available on devices released before Android 12, and is guaranteed + // to be available on devices first shipped with Android 12 or later. + public static final String AUTH_AES_CMAC = "cmac(aes)"; + + /** * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm. * * <p>Valid lengths for keying material are {160, 224, 288}. @@ -191,6 +210,7 @@ public final class IpSecAlgorithm implements Parcelable { AUTH_HMAC_SHA384, AUTH_HMAC_SHA512, AUTH_AES_XCBC, + AUTH_AES_CMAC, AUTH_CRYPT_AES_GCM, AUTH_CRYPT_CHACHA20_POLY1305 }) @@ -215,6 +235,7 @@ public final class IpSecAlgorithm implements Parcelable { // STOPSHIP: b/170424293 Use Build.VERSION_CODES.S when it is defined ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.R + 1); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1); } @@ -383,6 +404,10 @@ public final class IpSecAlgorithm implements Parcelable { isValidLen = keyLen == 128; isValidTruncLen = truncLen == 96; break; + case AUTH_AES_CMAC: + isValidLen = keyLen == 128; + isValidTruncLen = truncLen == 96; + break; case AUTH_CRYPT_AES_GCM: // The keying material for GCM is a key plus a 32-bit salt isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; @@ -416,6 +441,7 @@ public final class IpSecAlgorithm implements Parcelable { case AUTH_HMAC_SHA384: case AUTH_HMAC_SHA512: case AUTH_AES_XCBC: + case AUTH_AES_CMAC: return true; default: return false; diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index 303a40755d4e..a5ece7b713c7 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -18,7 +18,6 @@ package android.net; import static android.net.ConnectivityManager.TYPE_WIFI; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.wifi.WifiInfo; @@ -180,29 +179,42 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } /** - * Build a {@link NetworkIdentity} from the given {@link NetworkState} and {@code subType}, - * assuming that any mobile networks are using the current IMSI. The subType if applicable, - * should be set as one of the TelephonyManager.NETWORK_TYPE_* constants, or - * {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. + * Build a {@link NetworkIdentity} from the given {@link NetworkState} and + * {@code subType}, assuming that any mobile networks are using the current IMSI. + * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_* + * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. */ - public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state, - boolean defaultNetwork, @NetworkType int subType) { - final int legacyType = state.legacyNetworkType; + // TODO: Delete this function after NetworkPolicyManagerService finishes the migration. + public static NetworkIdentity buildNetworkIdentity(Context context, + NetworkState state, boolean defaultNetwork, @NetworkType int subType) { + final NetworkStateSnapshot snapshot = new NetworkStateSnapshot(state.network, + state.networkCapabilities, state.linkProperties, state.subscriberId, + state.legacyNetworkType); + return buildNetworkIdentity(context, snapshot, defaultNetwork, subType); + } - String subscriberId = null; + /** + * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and + * {@code subType}, assuming that any mobile networks are using the current IMSI. + * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_* + * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. + */ + public static NetworkIdentity buildNetworkIdentity(Context context, + NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) { + final int legacyType = snapshot.legacyType; + + final String subscriberId = snapshot.subscriberId; String networkId = null; - boolean roaming = !state.networkCapabilities.hasCapability( + boolean roaming = !snapshot.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); - boolean metered = !state.networkCapabilities.hasCapability( + boolean metered = !snapshot.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - subscriberId = state.subscriberId; - - final int oemManaged = getOemBitfield(state.networkCapabilities); + final int oemManaged = getOemBitfield(snapshot.networkCapabilities); if (legacyType == TYPE_WIFI) { - if (state.networkCapabilities.getSsid() != null) { - networkId = state.networkCapabilities.getSsid(); + if (snapshot.networkCapabilities.getSsid() != null) { + networkId = snapshot.networkCapabilities.getSsid(); if (networkId == null) { // TODO: Figure out if this code path never runs. If so, remove them. final WifiManager wifi = (WifiManager) context.getSystemService( diff --git a/core/java/android/net/NetworkStateSnapshot.java b/core/java/android/net/NetworkStateSnapshot.java index 881b373fa241..0d26c2de8698 100644 --- a/core/java/android/net/NetworkStateSnapshot.java +++ b/core/java/android/net/NetworkStateSnapshot.java @@ -16,11 +16,16 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.net.module.util.NetworkIdentityUtils; + import java.util.Objects; /** @@ -28,31 +33,49 @@ import java.util.Objects; * * @hide */ +@SystemApi(client = MODULE_LIBRARIES) public final class NetworkStateSnapshot implements Parcelable { + /** The network associated with this snapshot. */ @NonNull - public final LinkProperties linkProperties; + public final Network network; + + /** The {@link NetworkCapabilities} of the network associated with this snapshot. */ @NonNull public final NetworkCapabilities networkCapabilities; + + /** The {@link LinkProperties} of the network associated with this snapshot. */ @NonNull - public final Network network; + public final LinkProperties linkProperties; + + /** + * The Subscriber Id of the network associated with this snapshot. See + * {@link android.telephony.TelephonyManager#getSubscriberId()}. + */ @Nullable public final String subscriberId; + + /** + * The legacy type of the network associated with this snapshot. See + * {@code ConnectivityManager#TYPE_*}. + */ public final int legacyType; - public NetworkStateSnapshot(@NonNull LinkProperties linkProperties, - @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network, + public NetworkStateSnapshot(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, @Nullable String subscriberId, int legacyType) { - this.linkProperties = Objects.requireNonNull(linkProperties); - this.networkCapabilities = Objects.requireNonNull(networkCapabilities); this.network = Objects.requireNonNull(network); + this.networkCapabilities = Objects.requireNonNull(networkCapabilities); + this.linkProperties = Objects.requireNonNull(linkProperties); this.subscriberId = subscriberId; this.legacyType = legacyType; } + /** @hide */ public NetworkStateSnapshot(@NonNull Parcel in) { - linkProperties = in.readParcelable(null); - networkCapabilities = in.readParcelable(null); network = in.readParcelable(null); + networkCapabilities = in.readParcelable(null); + linkProperties = in.readParcelable(null); subscriberId = in.readString(); legacyType = in.readInt(); } @@ -64,9 +87,9 @@ public final class NetworkStateSnapshot implements Parcelable { @Override public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeParcelable(linkProperties, flags); - out.writeParcelable(networkCapabilities, flags); out.writeParcelable(network, flags); + out.writeParcelable(networkCapabilities, flags); + out.writeParcelable(linkProperties, flags); out.writeString(subscriberId); out.writeInt(legacyType); } @@ -93,14 +116,25 @@ public final class NetworkStateSnapshot implements Parcelable { if (!(o instanceof NetworkStateSnapshot)) return false; NetworkStateSnapshot that = (NetworkStateSnapshot) o; return legacyType == that.legacyType - && Objects.equals(linkProperties, that.linkProperties) - && Objects.equals(networkCapabilities, that.networkCapabilities) && Objects.equals(network, that.network) + && Objects.equals(networkCapabilities, that.networkCapabilities) + && Objects.equals(linkProperties, that.linkProperties) && Objects.equals(subscriberId, that.subscriberId); } @Override public int hashCode() { - return Objects.hash(linkProperties, networkCapabilities, network, subscriberId, legacyType); + return Objects.hash(network, networkCapabilities, linkProperties, subscriberId, legacyType); + } + + @Override + public String toString() { + return "NetworkStateSnapshot{" + + "network=" + network + + ", networkCapabilities=" + networkCapabilities + + ", linkProperties=" + linkProperties + + ", subscriberId='" + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + '\'' + + ", legacyType=" + legacyType + + '}'; } } diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java index b4034556f66e..48bd29769f83 100644 --- a/core/java/android/net/OemNetworkPreferences.java +++ b/core/java/android/net/OemNetworkPreferences.java @@ -29,7 +29,15 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -/** @hide */ +/** + * Network preferences to set the default active network on a per-application basis as per a given + * {@link OemNetworkPreference}. An example of this would be to set an application's network + * preference to {@link #OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK} which would have the default + * network for that application set to an unmetered network first if available and if not, it then + * set that application's default network to an OEM managed network if available. + * + * @hide + */ @SystemApi public final class OemNetworkPreferences implements Parcelable { /** @@ -64,6 +72,10 @@ public final class OemNetworkPreferences implements Parcelable { @NonNull private final Bundle mNetworkMappings; + /** + * Return the currently built application package name to {@link OemNetworkPreference} mappings. + * @return the current network preferences map. + */ @NonNull public Map<String, Integer> getNetworkPreferences() { return convertToUnmodifiableMap(mNetworkMappings); @@ -105,6 +117,11 @@ public final class OemNetworkPreferences implements Parcelable { mNetworkMappings = new Bundle(); } + /** + * Constructor to populate the builder's values with an already built + * {@link OemNetworkPreferences}. + * @param preferences the {@link OemNetworkPreferences} to populate with. + */ public Builder(@NonNull final OemNetworkPreferences preferences) { Objects.requireNonNull(preferences); mNetworkMappings = (Bundle) preferences.mNetworkMappings.clone(); diff --git a/packages/Connectivity/framework/src/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java index 326943a27d4e..326943a27d4e 100644 --- a/packages/Connectivity/framework/src/android/net/PacProxySelector.java +++ b/core/java/android/net/PacProxySelector.java diff --git a/packages/Connectivity/framework/src/android/net/Proxy.java b/core/java/android/net/Proxy.java index 77c8a4f4579b..77c8a4f4579b 100644 --- a/packages/Connectivity/framework/src/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index f90fbaf1e0fb..fa3ff8a26862 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -41,6 +41,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import com.android.internal.net.NetworkUtilsInternal; import com.android.internal.net.VpnConfig; import java.net.DatagramSocket; @@ -254,7 +255,7 @@ public class VpnService extends Service { * @return {@code true} on success. */ public boolean protect(int socket) { - return NetworkUtils.protectFromVpn(socket); + return NetworkUtilsInternal.protectFromVpn(socket); } /** diff --git a/packages/Connectivity/framework/src/android/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java index e64060f1b220..69edc757ce8a 100644 --- a/packages/Connectivity/framework/src/android/net/util/SocketUtils.java +++ b/core/java/android/net/util/SocketUtils.java @@ -22,12 +22,13 @@ import static android.system.OsConstants.SO_BINDTODEVICE; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.net.NetworkUtils; import android.system.ErrnoException; import android.system.NetlinkSocketAddress; import android.system.Os; import android.system.PacketSocketAddress; +import com.android.internal.net.NetworkUtilsInternal; + import libcore.io.IoBridge; import java.io.FileDescriptor; @@ -51,7 +52,7 @@ public final class SocketUtils { // of struct ifreq is a NULL-terminated interface name. // TODO: add a setsockoptString() Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface); - NetworkUtils.protectFromVpn(socket); + NetworkUtilsInternal.protectFromVpn(socket); } /** diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl index d91cef592d10..236ae8bb11b2 100644 --- a/core/java/android/net/vcn/IVcnStatusCallback.aidl +++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl @@ -18,7 +18,6 @@ package android.net.vcn; /** @hide */ oneway interface IVcnStatusCallback { - void onEnteredSafeMode(); void onVcnStatusChanged(int statusCode); void onGatewayConnectionError( in int[] gatewayNetworkCapabilities, diff --git a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl b/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl index f8ae492016f0..62de8216ce54 100644 --- a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl +++ b/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl @@ -17,6 +17,6 @@ package android.net.vcn; /** @hide */ -interface IVcnUnderlyingNetworkPolicyListener { +oneway interface IVcnUnderlyingNetworkPolicyListener { void onPolicyChanged(); }
\ No newline at end of file diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index eb8c251fec78..8ebf757760c3 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -359,8 +359,6 @@ public class VcnManager { /** * Value indicating that the VCN for the subscription group is not configured, or that the * callback is not privileged for the subscription group. - * - * @hide */ public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; @@ -369,8 +367,6 @@ public class VcnManager { * * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the * provisioning package is not privileged. - * - * @hide */ public static final int VCN_STATUS_CODE_INACTIVE = 1; @@ -380,8 +376,6 @@ public class VcnManager { * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered * active while it is connecting, fully connected, and disconnecting. - * - * @hide */ public static final int VCN_STATUS_CODE_ACTIVE = 2; @@ -391,8 +385,6 @@ public class VcnManager { * <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to * establish a connection within a system-determined timeout (while underlying networks were * available). - * - * @hide */ public static final int VCN_STATUS_CODE_SAFE_MODE = 3; @@ -407,8 +399,6 @@ public class VcnManager { /** * Value indicating that an internal failure occurred in this Gateway Connection. - * - * @hide */ public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; @@ -416,8 +406,6 @@ public class VcnManager { * Value indicating that an error with this Gateway Connection's configuration occurred. * * <p>For example, this error code will be returned after authentication failures. - * - * @hide */ public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; @@ -427,38 +415,19 @@ public class VcnManager { * <p>For example, this error code will be returned if an underlying {@link android.net.Network} * for this Gateway Connection is lost, or if an error occurs while resolving the connection * endpoint address. - * - * @hide */ public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; - // TODO: make VcnStatusCallback @SystemApi /** * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs. * * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a * subscription group. - * - * @hide */ public abstract static class VcnStatusCallback { private VcnStatusCallbackBinder mCbBinder; /** - * Invoked when the VCN for this Callback's subscription group enters safe mode. - * - * <p>A VCN will be put into safe mode if any of the gateway connections were unable to - * establish a connection within a system-determined timeout (while underlying networks were - * available). - * - * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration - * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}. - * - * @hide - */ - public void onEnteredSafeMode() {} - - /** * Invoked when status of the VCN for this callback's subscription group changes. * * @param statusCode the code for the status change encountered by this {@link @@ -467,15 +436,16 @@ public class VcnManager { public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode); /** - * Invoked when a VCN Gateway Connection corresponding to this callback's subscription + * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group * encounters an error. * - * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway - * Connection that encountered the error for identification purposes. These will be a - * sorted list with no duplicates, matching one of the {@link + * @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}s set in the {@link VcnConfig} for this subscription * group. - * @param errorCode {@link VcnErrorCode} to indicate the error that occurred + * @param errorCode the code to indicate the error that occurred * @param detail Throwable to provide additional information about the error, or {@code * null} if none */ @@ -496,6 +466,10 @@ public class VcnManager { * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier * privileges for the specified subscription at the time of invocation. * + * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered + * and there is a VCN active for its specified subscription group (this may happen after the + * callback is registered). + * * <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the * current status for the specified subscription group's VCN. If the registrant is not * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be @@ -505,7 +479,6 @@ public class VcnManager { * @param executor The {@link Executor} to be used for invoking callbacks * @param callback The VcnStatusCallback to be registered * @throws IllegalStateException if callback is currently registered with VcnManager - * @hide */ public void registerVcnStatusCallback( @NonNull ParcelUuid subscriptionGroup, @@ -538,7 +511,6 @@ public class VcnManager { * was registered with. * * @param callback The callback to be unregistered - * @hide */ public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) { requireNonNull(callback, "callback must not be null"); @@ -599,12 +571,6 @@ public class VcnManager { } @Override - public void onEnteredSafeMode() { - Binder.withCleanCallingIdentity( - () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode())); - } - - @Override public void onVcnStatusChanged(@VcnStatusCode int statusCode) { Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode))); diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java new file mode 100644 index 000000000000..b6036b4a6fd1 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vcn.persistablebundleutils; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Objects; + +/** + * CertUtils provides utility methods for constructing Certificate. + * + * @hide + */ +public class CertUtils { + private static final String CERT_TYPE_X509 = "X.509"; + + /** Decodes an ASN.1 DER encoded Certificate */ + public static X509Certificate certificateFromByteArray(byte[] derEncoded) { + Objects.requireNonNull(derEncoded, "derEncoded is null"); + + try { + CertificateFactory certFactory = CertificateFactory.getInstance(CERT_TYPE_X509); + InputStream in = new ByteArrayInputStream(derEncoded); + return (X509Certificate) certFactory.generateCertificate(in); + } catch (CertificateException e) { + throw new IllegalArgumentException("Fail to decode certificate", e); + } + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java new file mode 100644 index 000000000000..ce5ec75f01a2 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vcn.persistablebundleutils; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.ipsec.ike.ChildSaProposal; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.List; +import java.util.Objects; + +/** + * Provides utility methods to convert ChildSaProposal to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class ChildSaProposalUtils extends SaProposalUtilsBase { + /** Serializes a ChildSaProposal to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(ChildSaProposal proposal) { + return SaProposalUtilsBase.toPersistableBundle(proposal); + } + + /** Constructs a ChildSaProposal by deserializing a PersistableBundle. */ + @NonNull + public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final ChildSaProposal.Builder builder = new ChildSaProposal.Builder(); + + final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); + Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null"); + final List<EncryptionAlgoKeyLenPair> encryptList = + PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new); + for (EncryptionAlgoKeyLenPair t : encryptList) { + builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen); + } + + final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY); + Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null"); + for (int algo : integrityAlgoIdArray) { + builder.addIntegrityAlgorithm(algo); + } + + final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY); + Objects.requireNonNull(dhGroupArray, "DH Group array was null"); + for (int dh : dhGroupArray) { + builder.addDhGroup(dh); + } + + return builder.build(); + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java new file mode 100644 index 000000000000..853a52da766a --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vcn.persistablebundleutils; + + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.eap.EapSessionConfig; +import android.net.eap.EapSessionConfig.EapAkaConfig; +import android.net.eap.EapSessionConfig.EapAkaPrimeConfig; +import android.net.eap.EapSessionConfig.EapMethodConfig; +import android.net.eap.EapSessionConfig.EapMsChapV2Config; +import android.net.eap.EapSessionConfig.EapSimConfig; +import android.net.eap.EapSessionConfig.EapTtlsConfig; +import android.net.eap.EapSessionConfig.EapUiccConfig; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Objects; + +/** + * Provides utility methods to convert EapSessionConfig to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class EapSessionConfigUtils { + private static final String EAP_ID_KEY = "EAP_ID_KEY"; + private static final String EAP_SIM_CONFIG_KEY = "EAP_SIM_CONFIG_KEY"; + private static final String EAP_TTLS_CONFIG_KEY = "EAP_TTLS_CONFIG_KEY"; + private static final String EAP_AKA_CONFIG_KEY = "EAP_AKA_CONFIG_KEY"; + private static final String EAP_MSCHAP_V2_CONFIG_KEY = "EAP_MSCHAP_V2_CONFIG_KEY"; + private static final String EAP_AKA_PRIME_CONFIG_KEY = "EAP_AKA_PRIME_CONFIG_KEY"; + + /** Serializes an EapSessionConfig to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapSessionConfig config) { + final PersistableBundle result = new PersistableBundle(); + + result.putPersistableBundle( + EAP_ID_KEY, PersistableBundleUtils.fromByteArray(config.getEapIdentity())); + + if (config.getEapSimConfig() != null) { + result.putPersistableBundle( + EAP_SIM_CONFIG_KEY, + EapSimConfigUtils.toPersistableBundle(config.getEapSimConfig())); + } + + if (config.getEapTtlsConfig() != null) { + result.putPersistableBundle( + EAP_TTLS_CONFIG_KEY, + EapTtlsConfigUtils.toPersistableBundle(config.getEapTtlsConfig())); + } + + if (config.getEapAkaConfig() != null) { + result.putPersistableBundle( + EAP_AKA_CONFIG_KEY, + EapAkaConfigUtils.toPersistableBundle(config.getEapAkaConfig())); + } + + if (config.getEapMsChapV2Config() != null) { + result.putPersistableBundle( + EAP_MSCHAP_V2_CONFIG_KEY, + EapMsChapV2ConfigUtils.toPersistableBundle(config.getEapMsChapV2Config())); + } + + if (config.getEapAkaPrimeConfig() != null) { + result.putPersistableBundle( + EAP_AKA_PRIME_CONFIG_KEY, + EapAkaPrimeConfigUtils.toPersistableBundle(config.getEapAkaPrimeConfig())); + } + + return result; + } + + /** Constructs an EapSessionConfig by deserializing a PersistableBundle. */ + @NonNull + public static EapSessionConfig fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final EapSessionConfig.Builder builder = new EapSessionConfig.Builder(); + + final PersistableBundle eapIdBundle = in.getPersistableBundle(EAP_ID_KEY); + Objects.requireNonNull(eapIdBundle, "EAP ID was null"); + builder.setEapIdentity(PersistableBundleUtils.toByteArray(eapIdBundle)); + + final PersistableBundle simBundle = in.getPersistableBundle(EAP_SIM_CONFIG_KEY); + if (simBundle != null) { + EapSimConfigUtils.setBuilderByReadingPersistableBundle(simBundle, builder); + } + + final PersistableBundle ttlsBundle = in.getPersistableBundle(EAP_TTLS_CONFIG_KEY); + if (ttlsBundle != null) { + EapTtlsConfigUtils.setBuilderByReadingPersistableBundle(ttlsBundle, builder); + } + + final PersistableBundle akaBundle = in.getPersistableBundle(EAP_AKA_CONFIG_KEY); + if (akaBundle != null) { + EapAkaConfigUtils.setBuilderByReadingPersistableBundle(akaBundle, builder); + } + + final PersistableBundle msChapV2Bundle = in.getPersistableBundle(EAP_MSCHAP_V2_CONFIG_KEY); + if (msChapV2Bundle != null) { + EapMsChapV2ConfigUtils.setBuilderByReadingPersistableBundle(msChapV2Bundle, builder); + } + + final PersistableBundle akaPrimeBundle = in.getPersistableBundle(EAP_AKA_PRIME_CONFIG_KEY); + if (akaPrimeBundle != null) { + EapAkaPrimeConfigUtils.setBuilderByReadingPersistableBundle(akaPrimeBundle, builder); + } + + return builder.build(); + } + + private static class EapMethodConfigUtils { + private static final String METHOD_TYPE = "METHOD_TYPE"; + + /** Serializes an EapMethodConfig to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapMethodConfig config) { + final PersistableBundle result = new PersistableBundle(); + result.putInt(METHOD_TYPE, config.getMethodType()); + return result; + } + } + + private static class EapUiccConfigUtils extends EapMethodConfigUtils { + static final String SUB_ID_KEY = "SUB_ID_KEY"; + static final String APP_TYPE_KEY = "APP_TYPE_KEY"; + + @NonNull + protected static PersistableBundle toPersistableBundle(@NonNull EapUiccConfig config) { + final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config); + result.putInt(SUB_ID_KEY, config.getSubId()); + result.putInt(APP_TYPE_KEY, config.getAppType()); + + return result; + } + } + + private static final class EapSimConfigUtils extends EapUiccConfigUtils { + @NonNull + public static PersistableBundle toPersistableBundle(EapSimConfig config) { + return EapUiccConfigUtils.toPersistableBundle(config); + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapSimConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY)); + } + } + + private static class EapAkaConfigUtils extends EapUiccConfigUtils { + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapAkaConfig config) { + return EapUiccConfigUtils.toPersistableBundle(config); + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapAkaConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY)); + } + } + + private static final class EapAkaPrimeConfigUtils extends EapAkaConfigUtils { + private static final String NETWORK_NAME_KEY = "NETWORK_NAME_KEY"; + private static final String ALL_MISMATCHED_NETWORK_KEY = "ALL_MISMATCHED_NETWORK_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapAkaPrimeConfig config) { + final PersistableBundle result = EapUiccConfigUtils.toPersistableBundle(config); + result.putString(NETWORK_NAME_KEY, config.getNetworkName()); + result.putBoolean(ALL_MISMATCHED_NETWORK_KEY, config.allowsMismatchedNetworkNames()); + + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapAkaPrimeConfig( + in.getInt(SUB_ID_KEY), + in.getInt(APP_TYPE_KEY), + in.getString(NETWORK_NAME_KEY), + in.getBoolean(ALL_MISMATCHED_NETWORK_KEY)); + } + } + + private static final class EapMsChapV2ConfigUtils extends EapMethodConfigUtils { + private static final String USERNAME_KEY = "USERNAME_KEY"; + private static final String PASSWORD_KEY = "PASSWORD_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapMsChapV2Config config) { + final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config); + result.putString(USERNAME_KEY, config.getUsername()); + result.putString(PASSWORD_KEY, config.getPassword()); + + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + builder.setEapMsChapV2Config(in.getString(USERNAME_KEY), in.getString(PASSWORD_KEY)); + } + } + + private static final class EapTtlsConfigUtils extends EapMethodConfigUtils { + private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY"; + private static final String EAP_SESSION_CONFIG_KEY = "EAP_SESSION_CONFIG_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull EapTtlsConfig config) { + final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config); + try { + if (config.getServerCaCert() != null) { + final PersistableBundle caBundle = + PersistableBundleUtils.fromByteArray( + config.getServerCaCert().getEncoded()); + result.putPersistableBundle(TRUST_CERT_KEY, caBundle); + } + } catch (CertificateEncodingException e) { + throw new IllegalStateException("Fail to encode the certificate"); + } + + result.putPersistableBundle( + EAP_SESSION_CONFIG_KEY, + EapSessionConfigUtils.toPersistableBundle(config.getInnerEapSessionConfig())); + + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final PersistableBundle caBundle = in.getPersistableBundle(TRUST_CERT_KEY); + X509Certificate caCert = null; + if (caBundle != null) { + caCert = + CertUtils.certificateFromByteArray( + PersistableBundleUtils.toByteArray(caBundle)); + } + + final PersistableBundle eapSessionConfigBundle = + in.getPersistableBundle(EAP_SESSION_CONFIG_KEY); + Objects.requireNonNull(eapSessionConfigBundle, "Inner EAP Session Config was null"); + final EapSessionConfig eapSessionConfig = + EapSessionConfigUtils.fromPersistableBundle(eapSessionConfigBundle); + + builder.setEapTtlsConfig(caCert, eapSessionConfig); + } + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java new file mode 100644 index 000000000000..6acb34ebb78e --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vcn.persistablebundleutils; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.InetAddresses; +import android.net.ipsec.ike.IkeDerAsn1DnIdentification; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeIdentification; +import android.net.ipsec.ike.IkeIpv4AddrIdentification; +import android.net.ipsec.ike.IkeIpv6AddrIdentification; +import android.net.ipsec.ike.IkeKeyIdIdentification; +import android.net.ipsec.ike.IkeRfc822AddrIdentification; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.Objects; + +import javax.security.auth.x500.X500Principal; + +/** + * Abstract utility class to convert IkeIdentification to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class IkeIdentificationUtils { + private static final String ID_TYPE_KEY = "ID_TYPE_KEY"; + + private static final String DER_ASN1_DN_KEY = "DER_ASN1_DN_KEY"; + private static final String FQDN_KEY = "FQDN_KEY"; + private static final String KEY_ID_KEY = "KEY_ID_KEY"; + private static final String IP4_ADDRESS_KEY = "IP4_ADDRESS_KEY"; + private static final String IP6_ADDRESS_KEY = "IP6_ADDRESS_KEY"; + private static final String RFC822_ADDRESS_KEY = "RFC822_ADDRESS_KEY"; + + private static final int ID_TYPE_DER_ASN1_DN = 1; + private static final int ID_TYPE_FQDN = 2; + private static final int ID_TYPE_IPV4_ADDR = 3; + private static final int ID_TYPE_IPV6_ADDR = 4; + private static final int ID_TYPE_KEY_ID = 5; + private static final int ID_TYPE_RFC822_ADDR = 6; + + /** Serializes an IkeIdentification to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull IkeIdentification ikeId) { + if (ikeId instanceof IkeDerAsn1DnIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_DER_ASN1_DN); + IkeDerAsn1DnIdentification id = (IkeDerAsn1DnIdentification) ikeId; + result.putPersistableBundle( + DER_ASN1_DN_KEY, + PersistableBundleUtils.fromByteArray(id.derAsn1Dn.getEncoded())); + return result; + } else if (ikeId instanceof IkeFqdnIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_FQDN); + IkeFqdnIdentification id = (IkeFqdnIdentification) ikeId; + result.putString(FQDN_KEY, id.fqdn); + return result; + } else if (ikeId instanceof IkeIpv4AddrIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV4_ADDR); + IkeIpv4AddrIdentification id = (IkeIpv4AddrIdentification) ikeId; + result.putString(IP4_ADDRESS_KEY, id.ipv4Address.getHostAddress()); + return result; + } else if (ikeId instanceof IkeIpv6AddrIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV6_ADDR); + IkeIpv6AddrIdentification id = (IkeIpv6AddrIdentification) ikeId; + result.putString(IP6_ADDRESS_KEY, id.ipv6Address.getHostAddress()); + return result; + } else if (ikeId instanceof IkeKeyIdIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_KEY_ID); + IkeKeyIdIdentification id = (IkeKeyIdIdentification) ikeId; + result.putPersistableBundle(KEY_ID_KEY, PersistableBundleUtils.fromByteArray(id.keyId)); + return result; + } else if (ikeId instanceof IkeRfc822AddrIdentification) { + final PersistableBundle result = createPersistableBundle(ID_TYPE_RFC822_ADDR); + IkeRfc822AddrIdentification id = (IkeRfc822AddrIdentification) ikeId; + result.putString(RFC822_ADDRESS_KEY, id.rfc822Name); + return result; + } else { + throw new IllegalStateException("Unrecognized IkeIdentification subclass"); + } + } + + private static PersistableBundle createPersistableBundle(int idType) { + final PersistableBundle result = new PersistableBundle(); + result.putInt(ID_TYPE_KEY, idType); + return result; + } + + /** Constructs an IkeIdentification by deserializing a PersistableBundle. */ + @NonNull + public static IkeIdentification fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + int idType = in.getInt(ID_TYPE_KEY); + switch (idType) { + case ID_TYPE_DER_ASN1_DN: + final PersistableBundle dnBundle = in.getPersistableBundle(DER_ASN1_DN_KEY); + Objects.requireNonNull(dnBundle, "ASN1 DN was null"); + return new IkeDerAsn1DnIdentification( + new X500Principal(PersistableBundleUtils.toByteArray(dnBundle))); + case ID_TYPE_FQDN: + return new IkeFqdnIdentification(in.getString(FQDN_KEY)); + case ID_TYPE_IPV4_ADDR: + final String v4AddressStr = in.getString(IP4_ADDRESS_KEY); + Objects.requireNonNull(v4AddressStr, "IPv4 address was null"); + return new IkeIpv4AddrIdentification( + (Inet4Address) InetAddresses.parseNumericAddress(v4AddressStr)); + case ID_TYPE_IPV6_ADDR: + final String v6AddressStr = in.getString(IP6_ADDRESS_KEY); + Objects.requireNonNull(v6AddressStr, "IPv6 address was null"); + return new IkeIpv6AddrIdentification( + (Inet6Address) InetAddresses.parseNumericAddress(v6AddressStr)); + case ID_TYPE_KEY_ID: + final PersistableBundle keyIdBundle = in.getPersistableBundle(KEY_ID_KEY); + Objects.requireNonNull(in, "Key ID was null"); + return new IkeKeyIdIdentification(PersistableBundleUtils.toByteArray(keyIdBundle)); + case ID_TYPE_RFC822_ADDR: + return new IkeRfc822AddrIdentification(in.getString(RFC822_ADDRESS_KEY)); + default: + throw new IllegalStateException("Unrecognized IKE ID type: " + idType); + } + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java new file mode 100644 index 000000000000..1459671f4136 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.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 android.net.vcn.persistablebundleutils; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.ipsec.ike.IkeSaProposal; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.List; +import java.util.Objects; + +/** + * Provides utility methods to convert IkeSaProposal to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class IkeSaProposalUtils extends SaProposalUtilsBase { + private static final String PRF_KEY = "PRF_KEY"; + + /** Serializes an IkeSaProposal to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(IkeSaProposal proposal) { + final PersistableBundle result = SaProposalUtilsBase.toPersistableBundle(proposal); + + final int[] prfArray = + proposal.getPseudorandomFunctions().stream().mapToInt(i -> i).toArray(); + result.putIntArray(PRF_KEY, prfArray); + + return result; + } + + /** Constructs an IkeSaProposal by deserializing a PersistableBundle. */ + @NonNull + public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final IkeSaProposal.Builder builder = new IkeSaProposal.Builder(); + + final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); + Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null"); + final List<EncryptionAlgoKeyLenPair> encryptList = + PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new); + for (EncryptionAlgoKeyLenPair t : encryptList) { + builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen); + } + + final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY); + Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null"); + for (int algo : integrityAlgoIdArray) { + builder.addIntegrityAlgorithm(algo); + } + + final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY); + Objects.requireNonNull(dhGroupArray, "DH Group array was null"); + for (int dh : dhGroupArray) { + builder.addDhGroup(dh); + } + + final int[] prfArray = in.getIntArray(PRF_KEY); + Objects.requireNonNull(prfArray, "PRF array was null"); + for (int prf : prfArray) { + builder.addPseudorandomFunction(prf); + } + + return builder.build(); + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java new file mode 100644 index 000000000000..6bbc6b1e8218 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vcn.persistablebundleutils; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.InetAddresses; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * Provides utility methods to convert IkeTrafficSelector to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class IkeTrafficSelectorUtils { + private static final String START_PORT_KEY = "START_PORT_KEY"; + private static final String END_PORT_KEY = "END_PORT_KEY"; + private static final String START_ADDRESS_KEY = "START_ADDRESS_KEY"; + private static final String END_ADDRESS_KEY = "END_ADDRESS_KEY"; + + /** Constructs an IkeTrafficSelector by deserializing a PersistableBundle. */ + @NonNull + public static IkeTrafficSelector fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final int startPort = in.getInt(START_PORT_KEY); + final int endPort = in.getInt(END_PORT_KEY); + + final String startingAddress = in.getString(START_ADDRESS_KEY); + final String endingAddress = in.getString(END_ADDRESS_KEY); + Objects.requireNonNull(startingAddress, "startAddress was null"); + Objects.requireNonNull(startingAddress, "endAddress was null"); + + return new IkeTrafficSelector( + startPort, + endPort, + InetAddresses.parseNumericAddress(startingAddress), + InetAddresses.parseNumericAddress(endingAddress)); + } + + /** Serializes an IkeTrafficSelector to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull IkeTrafficSelector ts) { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(START_PORT_KEY, ts.startPort); + result.putInt(END_PORT_KEY, ts.endPort); + result.putString(START_ADDRESS_KEY, ts.startingAddress.getHostAddress()); + result.putString(END_ADDRESS_KEY, ts.endingAddress.getHostAddress()); + + return result; + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java new file mode 100644 index 000000000000..0c9ee8432798 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.vcn.persistablebundleutils; + +import android.annotation.NonNull; +import android.net.ipsec.ike.SaProposal; +import android.os.PersistableBundle; +import android.util.Pair; + +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Abstract utility class to convert SaProposal to/from PersistableBundle. + * + * @hide + */ +abstract class SaProposalUtilsBase { + static final String ENCRYPT_ALGO_KEY = "ENCRYPT_ALGO_KEY"; + static final String INTEGRITY_ALGO_KEY = "INTEGRITY_ALGO_KEY"; + static final String DH_GROUP_KEY = "DH_GROUP_KEY"; + + static class EncryptionAlgoKeyLenPair { + private static final String ALGO_KEY = "ALGO_KEY"; + private static final String KEY_LEN_KEY = "KEY_LEN_KEY"; + + public final int encryptionAlgo; + public final int keyLen; + + EncryptionAlgoKeyLenPair(int encryptionAlgo, int keyLen) { + this.encryptionAlgo = encryptionAlgo; + this.keyLen = keyLen; + } + + EncryptionAlgoKeyLenPair(PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + this.encryptionAlgo = in.getInt(ALGO_KEY); + this.keyLen = in.getInt(KEY_LEN_KEY); + } + + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(ALGO_KEY, encryptionAlgo); + result.putInt(KEY_LEN_KEY, keyLen); + + return result; + } + } + + /** + * Serializes common info of a SaProposal to a PersistableBundle. + * + * @hide + */ + @NonNull + static PersistableBundle toPersistableBundle(SaProposal proposal) { + final PersistableBundle result = new PersistableBundle(); + + final List<EncryptionAlgoKeyLenPair> encryptAlgoKeyLenPairs = new ArrayList<>(); + for (Pair<Integer, Integer> pair : proposal.getEncryptionAlgorithms()) { + encryptAlgoKeyLenPairs.add(new EncryptionAlgoKeyLenPair(pair.first, pair.second)); + } + final PersistableBundle encryptionBundle = + PersistableBundleUtils.fromList( + encryptAlgoKeyLenPairs, EncryptionAlgoKeyLenPair::toPersistableBundle); + result.putPersistableBundle(ENCRYPT_ALGO_KEY, encryptionBundle); + + final int[] integrityAlgoIdArray = + proposal.getIntegrityAlgorithms().stream().mapToInt(i -> i).toArray(); + result.putIntArray(INTEGRITY_ALGO_KEY, integrityAlgoIdArray); + + final int[] dhGroupArray = proposal.getDhGroups().stream().mapToInt(i -> i).toArray(); + result.putIntArray(DH_GROUP_KEY, dhGroupArray); + + return result; + } +} diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java new file mode 100644 index 000000000000..e62acac14bd7 --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vcn.persistablebundleutils; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.net.ipsec.ike.TunnelModeChildSessionParams; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address; +import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer; +import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest; +import android.os.PersistableBundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Provides utility methods to convert TunnelModeChildSessionParams to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class TunnelModeChildSessionParamsUtils { + private static final String TAG = TunnelModeChildSessionParamsUtils.class.getSimpleName(); + + private static final String INBOUND_TS_KEY = "INBOUND_TS_KEY"; + private static final String OUTBOUND_TS_KEY = "OUTBOUND_TS_KEY"; + private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY"; + private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY"; + private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY"; + private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY"; + + private static class ConfigRequest { + private static final int TYPE_IPV4_ADDRESS = 1; + private static final int TYPE_IPV6_ADDRESS = 2; + private static final int TYPE_IPV4_DNS = 3; + private static final int TYPE_IPV6_DNS = 4; + private static final int TYPE_IPV4_DHCP = 5; + private static final int TYPE_IPV4_NETMASK = 6; + + private static final String TYPE_KEY = "type"; + private static final String VALUE_KEY = "address"; + private static final String IP6_PREFIX_LEN = "ip6PrefixLen"; + + private static final int PREFIX_LEN_UNUSED = -1; + + public final int type; + public final int ip6PrefixLen; + + // Null when it is an empty request + @Nullable public final InetAddress address; + + ConfigRequest(TunnelModeChildConfigRequest config) { + int prefixLen = PREFIX_LEN_UNUSED; + + if (config instanceof ConfigRequestIpv4Address) { + type = TYPE_IPV4_ADDRESS; + address = ((ConfigRequestIpv4Address) config).getAddress(); + } else if (config instanceof ConfigRequestIpv6Address) { + type = TYPE_IPV6_ADDRESS; + address = ((ConfigRequestIpv6Address) config).getAddress(); + if (address != null) { + prefixLen = ((ConfigRequestIpv6Address) config).getPrefixLength(); + } + } else if (config instanceof ConfigRequestIpv4DnsServer) { + type = TYPE_IPV4_DNS; + address = null; + } else if (config instanceof ConfigRequestIpv6DnsServer) { + type = TYPE_IPV6_DNS; + address = null; + } else if (config instanceof ConfigRequestIpv4DhcpServer) { + type = TYPE_IPV4_DHCP; + address = null; + } else if (config instanceof ConfigRequestIpv4Netmask) { + type = TYPE_IPV4_NETMASK; + address = null; + } else { + throw new IllegalStateException("Unknown TunnelModeChildConfigRequest"); + } + + ip6PrefixLen = prefixLen; + } + + ConfigRequest(PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + type = in.getInt(TYPE_KEY); + ip6PrefixLen = in.getInt(IP6_PREFIX_LEN); + + String addressStr = in.getString(VALUE_KEY); + if (addressStr == null) { + address = null; + } else { + address = InetAddresses.parseNumericAddress(addressStr); + } + } + + @NonNull + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(TYPE_KEY, type); + result.putInt(IP6_PREFIX_LEN, ip6PrefixLen); + + if (address != null) { + result.putString(VALUE_KEY, address.getHostAddress()); + } + + return result; + } + } + + /** Serializes a TunnelModeChildSessionParams to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle( + @NonNull TunnelModeChildSessionParams params) { + final PersistableBundle result = new PersistableBundle(); + + final PersistableBundle saProposalBundle = + PersistableBundleUtils.fromList( + params.getSaProposals(), ChildSaProposalUtils::toPersistableBundle); + result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle); + + final PersistableBundle inTsBundle = + PersistableBundleUtils.fromList( + params.getInboundTrafficSelectors(), + IkeTrafficSelectorUtils::toPersistableBundle); + result.putPersistableBundle(INBOUND_TS_KEY, inTsBundle); + + final PersistableBundle outTsBundle = + PersistableBundleUtils.fromList( + params.getOutboundTrafficSelectors(), + IkeTrafficSelectorUtils::toPersistableBundle); + result.putPersistableBundle(OUTBOUND_TS_KEY, outTsBundle); + + result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds()); + result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds()); + + final List<ConfigRequest> reqList = new ArrayList<>(); + for (TunnelModeChildConfigRequest req : params.getConfigurationRequests()) { + reqList.add(new ConfigRequest(req)); + } + final PersistableBundle configReqListBundle = + PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle); + result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle); + + return result; + } + + private static List<IkeTrafficSelector> getTsFromPersistableBundle( + PersistableBundle in, String key) { + PersistableBundle tsBundle = in.getPersistableBundle(key); + Objects.requireNonNull(tsBundle, "Value for key " + key + " was null"); + return PersistableBundleUtils.toList( + tsBundle, IkeTrafficSelectorUtils::fromPersistableBundle); + } + + /** Constructs a TunnelModeChildSessionParams by deserializing a PersistableBundle. */ + @NonNull + public static TunnelModeChildSessionParams fromPersistableBundle( + @NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + final TunnelModeChildSessionParams.Builder builder = + new TunnelModeChildSessionParams.Builder(); + + final PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY); + Objects.requireNonNull(proposalBundle, "SA proposal was null"); + final List<ChildSaProposal> proposals = + PersistableBundleUtils.toList( + proposalBundle, ChildSaProposalUtils::fromPersistableBundle); + for (ChildSaProposal p : proposals) { + builder.addSaProposal(p); + } + + for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, INBOUND_TS_KEY)) { + builder.addInboundTrafficSelectors(ts); + } + + for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, OUTBOUND_TS_KEY)) { + builder.addOutboundTrafficSelectors(ts); + } + + builder.setLifetimeSeconds( + in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY)); + final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY); + Objects.requireNonNull(configReqListBundle, "Config request list was null"); + final List<ConfigRequest> reqList = + PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new); + + boolean hasIpv4AddressReq = false; + boolean hasIpv4NetmaskReq = false; + for (ConfigRequest req : reqList) { + switch (req.type) { + case ConfigRequest.TYPE_IPV4_ADDRESS: + hasIpv4AddressReq = true; + if (req.address == null) { + builder.addInternalAddressRequest(AF_INET); + } else { + builder.addInternalAddressRequest((Inet4Address) req.address); + } + break; + case ConfigRequest.TYPE_IPV6_ADDRESS: + if (req.address == null) { + builder.addInternalAddressRequest(AF_INET6); + } else { + builder.addInternalAddressRequest( + (Inet6Address) req.address, req.ip6PrefixLen); + } + break; + case ConfigRequest.TYPE_IPV4_NETMASK: + // Do not need to set netmask because it will be automatically set by the + // builder when an IPv4 internal address request is set. + hasIpv4NetmaskReq = true; + break; + case ConfigRequest.TYPE_IPV4_DNS: + if (req.address != null) { + Log.w(TAG, "Requesting a specific IPv4 DNS server is unsupported"); + } + builder.addInternalDnsServerRequest(AF_INET); + break; + case ConfigRequest.TYPE_IPV6_DNS: + if (req.address != null) { + Log.w(TAG, "Requesting a specific IPv6 DNS server is unsupported"); + } + builder.addInternalDnsServerRequest(AF_INET6); + break; + case ConfigRequest.TYPE_IPV4_DHCP: + if (req.address != null) { + Log.w(TAG, "Requesting a specific IPv4 DHCP server is unsupported"); + } + builder.addInternalDhcpServerRequest(AF_INET); + break; + default: + throw new IllegalArgumentException( + "Unrecognized config request type: " + req.type); + } + } + + if (hasIpv4AddressReq != hasIpv4NetmaskReq) { + Log.w( + TAG, + String.format( + "Expect IPv4 address request and IPv4 netmask request either both" + + " exist or both absent, but found hasIpv4AddressReq exists? %b," + + " hasIpv4AddressReq exists? %b, ", + hasIpv4AddressReq, hasIpv4NetmaskReq)); + } + + return builder.build(); + } +} diff --git a/core/java/android/os/BatterySaverPolicyConfig.java b/core/java/android/os/BatterySaverPolicyConfig.java index 81c781bff06d..a999e658ce21 100644 --- a/core/java/android/os/BatterySaverPolicyConfig.java +++ b/core/java/android/os/BatterySaverPolicyConfig.java @@ -247,6 +247,7 @@ public final class BatterySaverPolicyConfig implements Parcelable { /** * Get the SoundTrigger mode while in Battery Saver. */ + @PowerManager.SoundTriggerPowerSaveMode public int getSoundTriggerMode() { return mSoundTriggerMode; } diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index de7b88575e98..f12eb8853c55 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -37,6 +37,8 @@ public final class BatteryUsageStats implements Parcelable { private final long mStatsStartRealtimeMs; private final double mDischargedPowerLowerBound; private final double mDischargedPowerUpperBound; + private final long mBatteryTimeRemainingMs; + private final long mChargeTimeRemainingMs; private final ArrayList<UidBatteryConsumer> mUidBatteryConsumers; private final ArrayList<SystemBatteryConsumer> mSystemBatteryConsumers; private final ArrayList<UserBatteryConsumer> mUserBatteryConsumers; @@ -50,6 +52,8 @@ public final class BatteryUsageStats implements Parcelable { mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah; mHistoryBuffer = builder.mHistoryBuffer; mHistoryTagPool = builder.mHistoryTagPool; + mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs; + mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs; double totalPower = 0; @@ -110,6 +114,25 @@ public final class BatteryUsageStats implements Parcelable { } /** + * Returns an approximation for how much run time (in milliseconds) is remaining on + * the battery. Returns -1 if no time can be computed: either there is not + * enough current data to make a decision, or the battery is currently + * charging. + */ + public long getBatteryTimeRemainingMs() { + return mBatteryTimeRemainingMs; + } + + /** + * Returns an approximation for how much time (in milliseconds) remains until the battery + * is fully charged. Returns -1 if no time can be computed: either there is not + * enough current data to make a decision, or the battery is currently discharging. + */ + public long getChargeTimeRemainingMs() { + return mChargeTimeRemainingMs; + } + + /** * Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully * charged), in mAh */ @@ -156,6 +179,8 @@ public final class BatteryUsageStats implements Parcelable { mDischargePercentage = source.readInt(); mDischargedPowerLowerBound = source.readDouble(); mDischargedPowerUpperBound = source.readDouble(); + mBatteryTimeRemainingMs = source.readLong(); + mChargeTimeRemainingMs = source.readLong(); mUidBatteryConsumers = new ArrayList<>(); source.readParcelableList(mUidBatteryConsumers, getClass().getClassLoader()); mSystemBatteryConsumers = new ArrayList<>(); @@ -194,6 +219,8 @@ public final class BatteryUsageStats implements Parcelable { dest.writeInt(mDischargePercentage); dest.writeDouble(mDischargedPowerLowerBound); dest.writeDouble(mDischargedPowerUpperBound); + dest.writeLong(mBatteryTimeRemainingMs); + dest.writeLong(mChargeTimeRemainingMs); dest.writeParcelableList(mUidBatteryConsumers, flags); dest.writeParcelableList(mSystemBatteryConsumers, flags); dest.writeParcelableList(mUserBatteryConsumers, flags); @@ -237,6 +264,8 @@ public final class BatteryUsageStats implements Parcelable { private int mDischargePercentage; private double mDischargedPowerLowerBoundMah; private double mDischargedPowerUpperBoundMah; + private long mBatteryTimeRemainingMs = -1; + private long mChargeTimeRemainingMs = -1; private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders = new SparseArray<>(); private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders = @@ -289,6 +318,26 @@ public final class BatteryUsageStats implements Parcelable { } /** + * Sets an approximation for how much time (in milliseconds) remains until the battery + * is fully discharged. + */ + @NonNull + public Builder setBatteryTimeRemainingMs(long batteryTimeRemainingMs) { + mBatteryTimeRemainingMs = batteryTimeRemainingMs; + return this; + } + + /** + * Sets an approximation for how much time (in milliseconds) remains until the battery + * is fully charged. + */ + @NonNull + public Builder setChargeTimeRemainingMs(long chargeTimeRemainingMs) { + mChargeTimeRemainingMs = chargeTimeRemainingMs; + return this; + } + + /** * Sets the parceled recent history. */ @NonNull diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 74df1b2b9194..a5b0e8d149ef 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1288,10 +1288,12 @@ public class Build { public static final String HOST = getString("ro.build.host"); /** - * Returns true if we are running a debug build such as "user-debug" or "eng". - * @hide + * Returns true if the device is running a debuggable build such as "userdebug" or "eng". + * + * Debuggable builds allow users to gain root access via local shell, attach debuggers to any + * application regardless of whether they have the "debuggable" attribute set, or downgrade + * selinux into "permissive" mode in particular. */ - @UnsupportedAppUsage public static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1; diff --git a/core/java/android/os/BytesMatcher.java b/core/java/android/os/BytesMatcher.java new file mode 100644 index 000000000000..8537f47eb575 --- /dev/null +++ b/core/java/android/os/BytesMatcher.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 android.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.bluetooth.BluetoothUuid; +import android.net.MacAddress; +import android.util.Log; + +import com.android.internal.util.HexDump; + +import java.util.ArrayList; +import java.util.function.Predicate; + +/** + * Predicate that tests if a given {@code byte[]} value matches a set of + * configured rules. + * <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. + * <p> + * Matchers can contain rules of varying lengths, and tested values will only be + * matched against rules of the exact same length. This is designed to support + * {@link BluetoothUuid} style values which can be variable length. + * + * @hide + */ +public class BytesMatcher implements Predicate<byte[]> { + private static final String TAG = "BytesMatcher"; + + private static final char TYPE_ACCEPT = '+'; + private static final char TYPE_REJECT = '-'; + + 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; + + 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(); + } + + 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; + } + } + + /** + * 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 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)); + } + + /** + * 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 MacAddress} value matches the set of rules + * configured in this matcher. + */ + public boolean testMacAddress(@NonNull MacAddress value) { + return test(value.toByteArray()); + } + + /** + * 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 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; + } + + /** + * 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(); + } + + /** + * 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); + + int nextRule = value.indexOf(',', i); + int nextMask = value.indexOf('/', i); + + 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_ACCEPT: + matcher.addAcceptRule(ruleValue, ruleMask); + break; + case TYPE_REJECT: + matcher.addRejectRule(ruleValue, ruleMask); + break; + default: + Log.w(TAG, "Ignoring unknown type " + type); + break; + } + + i = nextRule + 1; + } + return matcher; + } +} diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java index c8e682c86ea7..e068772e954a 100644 --- a/core/java/android/os/CombinedVibrationEffect.java +++ b/core/java/android/os/CombinedVibrationEffect.java @@ -76,7 +76,9 @@ public abstract class CombinedVibrationEffect implements Parcelable { * A sequential vibration effect should be performed by multiple vibrators in order. * * @see CombinedVibrationEffect.SequentialCombination + * @hide */ + @TestApi @NonNull public static SequentialCombination startSequential() { return new SequentialCombination(); @@ -162,7 +164,9 @@ public abstract class CombinedVibrationEffect implements Parcelable { * A combination of haptic effects that should be played in multiple vibrators in sequence. * * @see CombinedVibrationEffect#startSequential() + * @hide */ + @TestApi public static final class SequentialCombination { private final ArrayList<CombinedVibrationEffect> mEffects = new ArrayList<>(); diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 874add5cdbd8..91d6a9bf69cb 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -23,7 +23,6 @@ import android.net.ITetheringStatsProvider; import android.net.Network; import android.net.NetworkStats; import android.net.RouteInfo; -import android.net.UidRange; /** * @hide @@ -182,11 +181,6 @@ interface INetworkManagementService String[] listTetheredInterfaces(); /** - * Sets the list of DNS forwarders (in order of priority) - */ - void setDnsForwarders(in Network network, in String[] dns); - - /** * Returns the list of DNS forwarders (in order of priority) */ String[] getDnsForwarders(); @@ -300,8 +294,6 @@ interface INetworkManagementService void setFirewallUidRules(int chain, in int[] uids, in int[] rules); void setFirewallChainEnabled(int chain, boolean enable); - void addLegacyRouteForNetId(int netId, in RouteInfo routeInfo, int uid); - /** * Allow UID to call protect(). */ diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 8f6161329e53..ae7d94cbad79 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -42,8 +42,7 @@ interface IPowerManager void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag); boolean isWakeLockLevelSupported(int level); - @UnsupportedAppUsage - void userActivity(long time, int event, int flags); + void userActivity(int displayId, long time, int event, int flags); void wakeUp(long time, int reason, String details, String opPackageName); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void goToSleep(long time, int reason, int flags); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index e5163d83de69..786a7d08047e 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1277,7 +1277,7 @@ public final class PowerManager { }) public void userActivity(long when, int event, int flags) { try { - mService.userActivity(when, event, flags); + mService.userActivity(mContext.getDisplayId(), when, event, flags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 0587610630a6..03e5f1d59b86 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -503,6 +503,20 @@ public abstract class VibrationEffect implements Parcelable { } /** @hide */ + public static String effectStrengthToString(int effectStrength) { + switch (effectStrength) { + case EFFECT_STRENGTH_LIGHT: + return "LIGHT"; + case EFFECT_STRENGTH_MEDIUM: + return "MEDIUM"; + case EFFECT_STRENGTH_STRONG: + return "STRONG"; + default: + return Integer.toString(effectStrength); + } + } + + /** @hide */ @TestApi public static class OneShot extends VibrationEffect implements Parcelable { private final long mDuration; @@ -936,8 +950,8 @@ public abstract class VibrationEffect implements Parcelable { @Override public String toString() { - return "Prebaked{mEffectId=" + mEffectId - + ", mEffectStrength=" + mEffectStrength + return "Prebaked{mEffectId=" + effectIdToString(mEffectId) + + ", mEffectStrength=" + effectStrengthToString(mEffectStrength) + ", mFallback=" + mFallback + ", mFallbackEffect=" + mFallbackEffect + "}"; diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 07272e756e77..50d2de3da965 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.vibrator.IVibrator; import android.util.SparseBooleanArray; import java.util.ArrayList; @@ -33,20 +34,7 @@ import java.util.Objects; * @hide */ public final class VibratorInfo implements Parcelable { - - /** - * Capability to set amplitude values to vibrations. - * @hide - */ - // Internally this maps to the HAL constant IVibrator::CAP_AMPLITUDE_CONTROL - public static final int CAPABILITY_AMPLITUDE_CONTROL = 4; - - /** - * Capability to compose primitives into a single effect. - * @hide - */ - // Internally this maps to the HAL constant IVibrator::CAP_COMPOSE_EFFECTS - public static final int CAPABILITY_COMPOSE_EFFECTS = 32; + private static final String TAG = "VibratorInfo"; private final int mId; private final long mCapabilities; @@ -108,7 +96,7 @@ public final class VibratorInfo implements Parcelable { return "VibratorInfo{" + "mId=" + mId + ", mCapabilities=" + Arrays.toString(getCapabilitiesNames()) - + ", mCapabilities flags=" + mCapabilities + + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities) + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames()) + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames()) + '}'; @@ -125,7 +113,7 @@ public final class VibratorInfo implements Parcelable { * @return True if the hardware can control the amplitude of the vibrations, otherwise false. */ public boolean hasAmplitudeControl() { - return hasCapability(CAPABILITY_AMPLITUDE_CONTROL); + return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL); } /** @@ -153,7 +141,7 @@ public final class VibratorInfo implements Parcelable { * @return Whether the primitive is supported. */ public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) { - return hasCapability(CAPABILITY_COMPOSE_EFFECTS) && mSupportedPrimitives != null + return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null && mSupportedPrimitives.get(primitiveId, false); } @@ -170,12 +158,27 @@ public final class VibratorInfo implements Parcelable { private String[] getCapabilitiesNames() { List<String> names = new ArrayList<>(); - if (hasCapability(CAPABILITY_AMPLITUDE_CONTROL)) { - names.add("AMPLITUDE_CONTROL"); + if (hasCapability(IVibrator.CAP_ON_CALLBACK)) { + names.add("ON_CALLBACK"); + } + if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) { + names.add("PERFORM_CALLBACK"); } - if (hasCapability(CAPABILITY_COMPOSE_EFFECTS)) { + if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { names.add("COMPOSE_EFFECTS"); } + if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { + names.add("ALWAYS_ON_CONTROL"); + } + if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { + names.add("AMPLITUDE_CONTROL"); + } + if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { + names.add("EXTERNAL_CONTROL"); + } + if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) { + names.add("EXTERNAL_AMPLITUDE_CONTROL"); + } return names.toArray(new String[names.size()]); } diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 9ffc5aa0022c..bf2898137967 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -333,7 +333,7 @@ public class ZygoteProcess { * started. * @param pkgDataInfoMap Map from related package names to private data directory * volume UUID and inode number. - * @param whitelistedDataInfoMap Map from allowlisted package names to private data directory + * @param allowlistedDataInfoList Map from allowlisted package names to private data directory * volume UUID and inode number. * @param bindMountAppsData whether zygote needs to mount CE and DE data. * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data. @@ -359,7 +359,7 @@ public class ZygoteProcess { @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap, @Nullable Map<String, Pair<String, Long>> - whitelistedDataInfoMap, + allowlistedDataInfoList, boolean bindMountAppsData, boolean bindMountAppStorageDirs, @Nullable String[] zygoteArgs) { @@ -373,7 +373,7 @@ public class ZygoteProcess { runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false, packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges, - pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData, + pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData, bindMountAppStorageDirs, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, @@ -615,7 +615,7 @@ public class ZygoteProcess { * @param disabledCompatChanges a list of disabled compat changes for the process being started. * @param pkgDataInfoMap Map from related package names to private data directory volume UUID * and inode number. - * @param whitelistedDataInfoMap Map from allowlisted package names to private data directory + * @param allowlistedDataInfoList Map from allowlisted package names to private data directory * volume UUID and inode number. * @param bindMountAppsData whether zygote needs to mount CE and DE data. * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data. @@ -642,7 +642,7 @@ public class ZygoteProcess { @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap, @Nullable Map<String, Pair<String, Long>> - whitelistedDataInfoMap, + allowlistedDataInfoList, boolean bindMountAppsData, boolean bindMountAppStorageDirs, @Nullable String[] extraArgs) @@ -733,12 +733,12 @@ public class ZygoteProcess { } argsForZygote.add(sb.toString()); } - if (whitelistedDataInfoMap != null && whitelistedDataInfoMap.size() > 0) { + if (allowlistedDataInfoList != null && allowlistedDataInfoList.size() > 0) { StringBuilder sb = new StringBuilder(); - sb.append(Zygote.WHITELISTED_DATA_INFO_MAP); + sb.append(Zygote.ALLOWLISTED_DATA_INFO_MAP); sb.append("="); boolean started = false; - for (Map.Entry<String, Pair<String, Long>> entry : whitelistedDataInfoMap.entrySet()) { + for (Map.Entry<String, Pair<String, Long>> entry : allowlistedDataInfoList.entrySet()) { if (started) { sb.append(','); } @@ -1318,7 +1318,7 @@ public class ZygoteProcess { true /* startChildZygote */, null /* packageName */, ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */, null /* disabledCompatChanges */, null /* pkgDataInfoMap */, - null /* whitelistedDataInfoMap */, true /* bindMountAppsData*/, + null /* allowlistedDataInfoList */, true /* bindMountAppsData*/, /* bindMountAppStorageDirs */ false, extraArgs); } catch (ZygoteStartFailedEx ex) { diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 592e98abae63..dc6f63a94685 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -58,6 +58,8 @@ public final class IncrementalManager { private static final String ALLOWED_PROPERTY = "incremental.allowed"; + public static final int MIN_VERSION_TO_SUPPORT_FSVERITY = 2; + public static final int CREATE_MODE_TEMPORARY_BIND = IIncrementalService.CREATE_MODE_TEMPORARY_BIND; public static final int CREATE_MODE_PERMANENT_BIND = @@ -155,22 +157,21 @@ public final class IncrementalManager { } /** - * Set up an app's code path. The expected outcome of this method is: + * Link an app's files from the stage dir to the final installation location. + * The expected outcome of this method is: * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory * of {@code afterCodeFile}. * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}. * * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it. - * Should no longer have any APKs after this method is called. * Example: /data/app/vmdl*tmp * @param afterCodeFile Path that should will have APKs after this method is called. Its parent * directory should be bind-mounted to a directory under /data/incremental. * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB] * @throws IllegalArgumentException * @throws IOException - * TODO(b/147371381): add unit tests */ - public void renameCodePath(File beforeCodeFile, File afterCodeFile) + public void linkCodePath(File beforeCodeFile, File afterCodeFile) throws IllegalArgumentException, IOException { final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile(); final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString()); @@ -188,7 +189,6 @@ public final class IncrementalManager { try { final String afterCodePathName = afterCodeFile.getName(); linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName); - apkStorage.unBind(beforeCodeAbsolute.toString()); } catch (Exception e) { linkedApkStorage.unBind(targetStorageDir); throw e; diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 0041699df9ef..98b4e0b4f402 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -28,6 +28,8 @@ import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import com.android.internal.os.AppFuseMount; +import android.app.PendingIntent; + /** * WARNING! Update IMountService.h and IMountService.cpp if you change this @@ -198,4 +200,5 @@ interface IStorageManager { void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90; void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91; void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92; + PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 7c8874cc1ea7..c967deb5e810 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -49,6 +49,7 @@ import android.app.Activity; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -701,6 +702,33 @@ public class StorageManager { } } + /** + * Returns a {@link PendingIntent} that can be used by Apps with + * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission + * to launch the manageSpaceActivity for any App that implements it, irrespective of its + * exported status. + * <p> + * Caller has the responsibility of supplying a valid packageName which has + * manageSpaceActivity implemented. + * + * @param packageName package name for the App for which manageSpaceActivity is to be launched + * @param requestCode for launching the activity + * @return PendingIntent to launch the manageSpaceActivity if successful, null if the + * packageName doesn't have a manageSpaceActivity. + * @throws IllegalArgumentException an invalid packageName is supplied. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) + @Nullable + public PendingIntent getManageSpaceActivityIntent( + @NonNull String packageName, int requestCode) { + try { + return mStorageManager.getManageSpaceActivityIntent(packageName, + requestCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private ObbInfo getObbInfo(String canonicalPath) { try { final ObbInfo obbInfo = ObbScanner.getObbInfo(canonicalPath); @@ -2738,10 +2766,11 @@ public class StorageManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void notifyAppIoBlocked(@NonNull String volumeUuid, int uid, int tid, + public void notifyAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid, @AppIoBlockedReason int reason) { + Objects.requireNonNull(volumeUuid); try { - mStorageManager.notifyAppIoBlocked(volumeUuid, uid, tid, reason); + mStorageManager.notifyAppIoBlocked(convert(volumeUuid), uid, tid, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2764,10 +2793,11 @@ public class StorageManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void notifyAppIoResumed(@NonNull String volumeUuid, int uid, int tid, + public void notifyAppIoResumed(@NonNull UUID volumeUuid, int uid, int tid, @AppIoBlockedReason int reason) { + Objects.requireNonNull(volumeUuid); try { - mStorageManager.notifyAppIoResumed(volumeUuid, uid, tid, reason); + mStorageManager.notifyAppIoResumed(convert(volumeUuid), uid, tid, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index b12bb2ece4c2..396ba2d3cea5 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IVold; +import java.util.List; import java.util.Set; /** @@ -112,4 +113,10 @@ public abstract class StorageManagerInternal { * @param bytes number of bytes which need to be freed */ public abstract void freeCache(@Nullable String volumeUuid, long bytes); + + /** + * Returns the {@link VolumeInfo#getId()} values for the volumes matching + * {@link VolumeInfo#isPrimary()} + */ + public abstract List<String> getPrimaryVolumeIds(); } diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index b32346848a69..19a3a8bd514a 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,7 +1,13 @@ # Bug component: 137825 +eugenesusla@google.com evanseverson@google.com +evanxinchen@google.com +ewol@google.com +guojing@google.com +jaysullivan@google.com ntmyren@google.com -zhanghai@google.com svetoslavganov@android.com svetoslavganov@google.com +theianchen@google.com +zhanghai@google.com diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 084b18eb2999..913b827332bf 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -668,7 +668,7 @@ public final class PermissionControllerManager { public void getPrivilegesDescriptionStringForProfile( @NonNull String profileName, @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<String> callback) { + @NonNull Consumer<CharSequence> callback) { mRemoteService.postAsync(service -> { AndroidFuture<String> future = new AndroidFuture<>(); service.getPrivilegesDescriptionStringForProfile(profileName, future); diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS index b32346848a69..fb6099cf7e5a 100644 --- a/core/java/android/permissionpresenterservice/OWNERS +++ b/core/java/android/permissionpresenterservice/OWNERS @@ -1,7 +1,3 @@ # Bug component: 137825 -evanseverson@google.com -ntmyren@google.com -zhanghai@google.com -svetoslavganov@android.com -svetoslavganov@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6e89faf9c2ed..e9bbcc79e9df 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -505,6 +505,22 @@ public final class DeviceConfig { "connectivity_thermal_power_manager"; /** + * Namespace for all statsd java features that can be applied immediately. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_STATSD_JAVA = "statsd_java"; + + /** + * Namespace for all statsd java features that are applied on boot. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot"; + + /** * Namespace for all statsd native features that can be applied immediately. * * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 617220e00f9d..85cef84d8d30 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16564,30 +16564,6 @@ public final class Settings { /** * Performs a strict and comprehensive check of whether a calling package is allowed to - * change the state of network, as the condition differs for pre-M, M+, and - * privileged/preinstalled apps. The caller is expected to have either the - * CHANGE_NETWORK_STATE or the WRITE_SETTINGS permission declared. Either of these - * permissions allow changing network state; WRITE_SETTINGS is a runtime permission and - * can be revoked, but (except in M, excluding M MRs), CHANGE_NETWORK_STATE is a normal - * permission and cannot be revoked. See http://b/23597341 - * - * Note: if the check succeeds because the application holds WRITE_SETTINGS, the operation - * of this app will be updated to the current time. - * @hide - */ - public static boolean checkAndNoteChangeNetworkStateOperation(Context context, int uid, - String callingPackage, String callingAttributionTag, boolean throwException) { - if (context.checkCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid, - callingPackage, callingAttributionTag, throwException, - AppOpsManager.OP_WRITE_SETTINGS, PM_CHANGE_NETWORK_STATE, true); - } - - /** - * Performs a strict and comprehensive check of whether a calling package is allowed to * draw on top of other apps, as the conditions differs for pre-M, M+, and * privileged/preinstalled apps. If the provided uid does not match the callingPackage, * a negative result will be returned. diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 7996f090b1a4..8a4812a42c8a 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -5302,5 +5302,13 @@ public final class Telephony { * @hide */ public static final String COLUMN_RCS_CONFIG = "rcs_config"; + + /** + * TelephonyProvider column name for VoIMS provisioning. Default is 0. + * <P>Type: INTEGER </P> + * + * @hide + */ + public static final String COLUMN_VOIMS_OPT_IN_STATUS = "voims_opt_in_status"; } } diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index fbc25a6aaf74..4c8ee598f512 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -18,10 +18,13 @@ package android.service.autofill; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; @@ -44,6 +47,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * {@link ServiceInfo} and meta-data about an {@link AutofillService}. @@ -76,6 +81,8 @@ public final class AutofillServiceInfo { @Nullable private final String mSettingsActivity; + @Nullable + private final String mPasswordsActivity; @Nullable private final ArrayMap<String, Long> mCompatibilityPackages; @@ -113,12 +120,14 @@ public final class AutofillServiceInfo { AutofillService.SERVICE_META_DATA); if (parser == null) { mSettingsActivity = null; + mPasswordsActivity = null; mCompatibilityPackages = null; mInlineSuggestionsEnabled = false; return; } String settingsActivity = null; + String passwordsActivity = null; ArrayMap<String, Long> compatibilityPackages = null; boolean inlineSuggestionsEnabled = false; // false by default. @@ -139,6 +148,8 @@ public final class AutofillServiceInfo { com.android.internal.R.styleable.AutofillService); settingsActivity = afsAttributes.getString( R.styleable.AutofillService_settingsActivity); + passwordsActivity = afsAttributes.getString( + R.styleable.AutofillService_passwordsActivity); inlineSuggestionsEnabled = afsAttributes.getBoolean( R.styleable.AutofillService_supportsInlineSuggestions, false); } finally { @@ -155,6 +166,7 @@ public final class AutofillServiceInfo { } mSettingsActivity = settingsActivity; + mPasswordsActivity = passwordsActivity; mCompatibilityPackages = compatibilityPackages; mInlineSuggestionsEnabled = inlineSuggestionsEnabled; } @@ -221,6 +233,7 @@ public final class AutofillServiceInfo { return compatibilityPackages; } + @NonNull public ServiceInfo getServiceInfo() { return mServiceInfo; } @@ -230,6 +243,12 @@ public final class AutofillServiceInfo { return mSettingsActivity; } + @Nullable + public String getPasswordsActivity() { + return mPasswordsActivity; + } + + @Nullable public ArrayMap<String, Long> getCompatibilityPackages() { return mCompatibilityPackages; } @@ -238,12 +257,37 @@ public final class AutofillServiceInfo { return mInlineSuggestionsEnabled; } + /** + * Queries the valid autofill services available for the user. + */ + public static List<AutofillServiceInfo> getAvailableServices( + Context context, @UserIdInt int user) { + final List<AutofillServiceInfo> services = new ArrayList<>(); + + final List<ResolveInfo> resolveInfos = + context.getPackageManager().queryIntentServicesAsUser( + new Intent(AutofillService.SERVICE_INTERFACE), + PackageManager.GET_META_DATA, + user); + for (ResolveInfo resolveInfo : resolveInfos) { + final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + try { + services.add(new AutofillServiceInfo(context, serviceInfo)); + } catch (SecurityException e) { + // Service does not declare the proper permission, ignore it. + Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e); + } + } + return services; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append(getClass().getSimpleName()); builder.append("[").append(mServiceInfo); builder.append(", settings:").append(mSettingsActivity); + builder.append(", passwords activity:").append(mPasswordsActivity); builder.append(", hasCompatPckgs:").append(mCompatibilityPackages != null && !mCompatibilityPackages.isEmpty()).append("]"); builder.append(", inline suggestions enabled:").append(mInlineSuggestionsEnabled); @@ -256,6 +300,7 @@ public final class AutofillServiceInfo { public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("Component: "); pw.println(getServiceInfo().getComponentName()); pw.print(prefix); pw.print("Settings: "); pw.println(mSettingsActivity); + pw.print(prefix); pw.print("Passwords activity: "); pw.println(mPasswordsActivity); pw.print(prefix); pw.print("Compat packages: "); pw.println(mCompatibilityPackages); pw.print(prefix); pw.print("Inline Suggestions Enabled: "); pw.println(mInlineSuggestionsEnabled); diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java index 1e07a8748af9..bbe184bd1a8c 100644 --- a/core/java/android/service/storage/ExternalStorageService.java +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -239,14 +239,13 @@ public abstract class ExternalStorageService extends Service { } @Override - public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason, - RemoteCallback callback) throws RemoteException { + public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason) + throws RemoteException { mHandler.post(() -> { try { onAnrDelayStarted(packageName, uid, tid, reason); - sendResult(packageName, null /* throwable */, callback); } catch (Throwable t) { - sendResult(packageName, t, callback); + // Ignored } }); } diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl index ba98efa58f7c..0766b754e57d 100644 --- a/core/java/android/service/storage/IExternalStorageService.aidl +++ b/core/java/android/service/storage/IExternalStorageService.aidl @@ -32,6 +32,5 @@ oneway interface IExternalStorageService in RemoteCallback callback); void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes, in RemoteCallback callback); - void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason, - in RemoteCallback callback); + void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason); }
\ No newline at end of file diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index e7ceada6180a..e37921ec03cc 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -17,10 +17,7 @@ package android.telephony; import android.Manifest; -import android.annotation.CallbackExecutor; -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.compat.annotation.ChangeId; @@ -31,16 +28,12 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.telephony.Annotation.CallState; -import android.telephony.Annotation.DataActivityType; import android.telephony.Annotation.DisconnectCauses; -import android.telephony.Annotation.NetworkType; import android.telephony.Annotation.PreciseDisconnectCauses; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SimActivationState; import android.telephony.Annotation.SrvccState; -import android.telephony.NetworkRegistrationInfo.Domain; import android.telephony.TelephonyManager.DataEnabledReason; -import android.telephony.TelephonyManager.DataState; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; @@ -49,8 +42,6 @@ import com.android.internal.telephony.IPhoneStateListener; import dalvik.system.VMRuntime; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.List; import java.util.Map; @@ -71,49 +62,15 @@ import java.util.concurrent.Executor; * information unless it has the appropriate permissions declared in * its manifest file. Where permissions apply, they are noted in the * appropriate LISTEN_ flags. + * + * @deprecated Use {@link TelephonyCallback} instead. */ +@Deprecated public class PhoneStateListener { private static final String LOG_TAG = "PhoneStateListener"; private static final boolean DBG = false; // STOPSHIP if true /** - * Experiment flag to set the per-pid registration limit for PhoneStateListeners - * - * Limit on registrations of {@link PhoneStateListener}s on a per-pid - * basis. When this limit is exceeded, any calls to {@link TelephonyManager#listen} will fail - * with an {@link IllegalStateException}. - * - * {@link android.os.Process#PHONE_UID}, {@link android.os.Process#SYSTEM_UID}, and the uid that - * TelephonyRegistry runs under are exempt from this limit. - * - * If the value of the flag is less than 1, enforcement of the limit will be disabled. - * @hide - */ - public static final String FLAG_PER_PID_REGISTRATION_LIMIT = - "phone_state_listener_per_pid_registration_limit"; - - /** - * Default value for the per-pid registation limit. - * See {@link #FLAG_PER_PID_REGISTRATION_LIMIT}. - * @hide - */ - public static final int DEFAULT_PER_PID_REGISTRATION_LIMIT = 50; - - /** - * This change enables a limit on the number of {@link PhoneStateListener} objects any process - * may register via {@link TelephonyManager#listen}. The default limit is 50, which may change - * via remote device config updates. - * - * This limit is enforced via an {@link IllegalStateException} thrown from - * {@link TelephonyManager#listen} when the offending process attempts to register one too many - * listeners. - * - * @hide - */ - @ChangeId - public static final long PHONE_STATE_LISTENER_LIMIT_CHANGE_ID = 150880553L; - - /** * Stop listening for updates. * * The PhoneStateListener is not tied to any subscription and unregistered for any update. @@ -125,7 +82,7 @@ public class PhoneStateListener { * * @see #onServiceStateChanged * @see ServiceState - * @deprecated Use {@link ServiceStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.ServiceStateListener} instead. */ @Deprecated public static final int LISTEN_SERVICE_STATE = 0x00000001; @@ -135,7 +92,7 @@ public class PhoneStateListener { * {@more} * * @see #onSignalStrengthChanged - * @deprecated Use {@link SignalStrengthsChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.SignalStrengthsListener} instead. */ @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 0x00000002; @@ -151,7 +108,7 @@ public class PhoneStateListener { * voicemail icon. * * @see #onMessageWaitingIndicatorChanged - * @deprecated Use {@link MessageWaitingIndicatorChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.MessageWaitingIndicatorListener} instead. */ @Deprecated public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 0x00000004; @@ -164,7 +121,7 @@ public class PhoneStateListener { * {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onCallForwardingIndicatorChanged - * @deprecated Use {@link CallForwardingIndicatorChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.CallForwardingIndicatorListener} instead. */ @Deprecated public static final int LISTEN_CALL_FORWARDING_INDICATOR = 0x00000008; @@ -182,7 +139,7 @@ public class PhoneStateListener { * instead. * * @see #onCellLocationChanged - * @deprecated Use {@link CellLocationChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.CellLocationListener} instead. */ @Deprecated public static final int LISTEN_CELL_LOCATION = 0x00000010; @@ -192,7 +149,7 @@ public class PhoneStateListener { * {@more} * * @see #onCallStateChanged - * @deprecated Use {@link CallStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.CallStateListener} instead. */ @Deprecated public static final int LISTEN_CALL_STATE = 0x00000020; @@ -201,7 +158,7 @@ public class PhoneStateListener { * Listen for changes to the data connection state (cellular). * * @see #onDataConnectionStateChanged - * @deprecated Use {@link DataConnectionStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.DataConnectionStateListener} instead. */ @Deprecated public static final int LISTEN_DATA_CONNECTION_STATE = 0x00000040; @@ -214,7 +171,7 @@ public class PhoneStateListener { * data-traffic icon. * * @see #onDataActivity - * @deprecated Use {@link DataActivityListener} instead. + * @deprecated Use {@link TelephonyCallback.DataActivityListener} instead. */ @Deprecated public static final int LISTEN_DATA_ACTIVITY = 0x00000080; @@ -226,7 +183,7 @@ public class PhoneStateListener { * icon. * * @see #onSignalStrengthsChanged - * @deprecated Use {@link SignalStrengthsChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.SignalStrengthsListener} instead. */ @Deprecated public static final int LISTEN_SIGNAL_STRENGTHS = 0x00000100; @@ -238,7 +195,8 @@ public class PhoneStateListener { * @see #onSignalStrengthsChanged * * @hide - * @deprecated Use {@link AlwaysReportedSignalStrengthChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.AlwaysReportedSignalStrengthListener} + * instead. */ @Deprecated @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) @@ -251,7 +209,7 @@ public class PhoneStateListener { * permission. * * @see #onCellInfoChanged - * @deprecated Use {@link CellInfoChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.CellInfoListener} instead. */ @Deprecated public static final int LISTEN_CELL_INFO = 0x00000400; @@ -265,7 +223,7 @@ public class PhoneStateListener { * (see {@link TelephonyManager#hasCarrierPrivileges}). * * @hide - * @deprecated Use {@link PreciseCallStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.PreciseCallStateListener} instead. */ @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -280,7 +238,7 @@ public class PhoneStateListener { * (see {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onPreciseDataConnectionStateChanged - * @deprecated Use {@link PreciseDataConnectionStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.PreciseDataConnectionStateListener} instead. */ @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -306,7 +264,7 @@ public class PhoneStateListener { * * @see #onServiceStateChanged(ServiceState) * @hide - * @deprecated Use {@link SrvccStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.SrvccStateListener} instead. */ @Deprecated @SystemApi @@ -328,7 +286,7 @@ public class PhoneStateListener { * * @see android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean) * @hide - * @deprecated Use {@link CarrierNetworkChangeListener} instead. + * @deprecated Use {@link TelephonyCallback.CarrierNetworkListener} instead. */ @Deprecated public static final int LISTEN_CARRIER_NETWORK_CHANGE = 0x00010000; @@ -349,7 +307,7 @@ public class PhoneStateListener { * * @see #onVoiceActivationStateChanged * @hide - * @deprecated Use {@link VoiceActivationStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.VoiceActivationStateListener} instead. */ @Deprecated @SystemApi @@ -369,7 +327,7 @@ public class PhoneStateListener { * * @see #onDataActivationStateChanged * @hide - * @deprecated Use {@link DataActivationStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.DataActivationStateListener} instead. */ @Deprecated public static final int LISTEN_DATA_ACTIVATION_STATE = 0x00040000; @@ -378,7 +336,7 @@ public class PhoneStateListener { * Listen for changes to the user mobile data state * * @see #onUserMobileDataStateChanged - * @deprecated Use {@link UserMobileDataStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.UserMobileDataStateListener} instead. */ @Deprecated public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000; @@ -391,7 +349,7 @@ public class PhoneStateListener { * {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onDisplayInfoChanged - * @deprecated Use {@link DisplayInfoChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead. */ @Deprecated public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000; @@ -401,7 +359,7 @@ public class PhoneStateListener { * * @see #onPhoneCapabilityChanged * @hide - * @deprecated Use {@link PhoneCapabilityChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.PhoneCapabilityListener} instead. */ @Deprecated public static final int LISTEN_PHONE_CAPABILITY_CHANGE = 0x00200000; @@ -413,7 +371,7 @@ public class PhoneStateListener { * subscription user selected as default data subscription in DSDS mode. * * @see #onActiveDataSubscriptionIdChanged - * @deprecated Use {@link ActiveDataSubscriptionIdChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.ActiveDataSubscriptionIdListener} instead. */ @Deprecated public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000; @@ -423,7 +381,7 @@ public class PhoneStateListener { * * @see #onRadioPowerStateChanged * @hide - * @deprecated Use {@link RadioPowerStateChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.RadioPowerStateListener} instead. */ @Deprecated @SystemApi @@ -436,7 +394,7 @@ public class PhoneStateListener { * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * - * @deprecated Use {@link EmergencyNumberListChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.EmergencyNumberListListener} instead. */ @Deprecated public static final int LISTEN_EMERGENCY_NUMBER_LIST = 0x01000000; @@ -449,7 +407,7 @@ public class PhoneStateListener { * or the calling app has carrier privileges * (see {@link TelephonyManager#hasCarrierPrivileges}). * - * @deprecated Use {@link CallDisconnectCauseChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.CallDisconnectCauseListener} instead. */ @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -464,7 +422,7 @@ public class PhoneStateListener { * * @see #onCallAttributesChanged * @hide - * @deprecated Use {@link CallAttributesChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.CallAttributesListener} instead. */ @Deprecated @SystemApi @@ -480,7 +438,7 @@ public class PhoneStateListener { * (see {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onImsCallDisconnectCauseChanged(ImsReasonInfo) - * @deprecated Use {@link ImsCallDisconnectCauseChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.ImsCallDisconnectCauseListener} instead. */ @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -491,7 +449,7 @@ public class PhoneStateListener { * * @see #onOutgoingEmergencyCall * @hide - * @deprecated Use {@link OutgoingEmergencyCallListener} instead. + * @deprecated Use {@link TelephonyCallback.OutgoingEmergencyCallListener} instead. */ @Deprecated @SystemApi @@ -503,7 +461,7 @@ public class PhoneStateListener { * * @see #onOutgoingEmergencySms * @hide - * @deprecated Use {@link OutgoingEmergencySmsListener} instead. + * @deprecated Use {@link TelephonyCallback.OutgoingEmergencySmsListener} instead. */ @Deprecated @SystemApi @@ -524,7 +482,7 @@ public class PhoneStateListener { * of whether the calling app has carrier privileges. * * @see #onRegistrationFailed - * @deprecated Use {@link RegistrationFailedListener} instead. + * @deprecated Use {@link TelephonyCallback.RegistrationFailedListener} instead. */ @Deprecated @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -540,540 +498,12 @@ public class PhoneStateListener { * of whether the calling app has carrier privileges. * * @see #onBarringInfoChanged - * @deprecated Use {@link BarringInfoChangedListener} instead. + * @deprecated Use {@link TelephonyCallback.BarringInfoListener} instead. */ @Deprecated @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = 0x80000000; - /** - * Event for changes to the network service state (cellular). - * - * @see ServiceStateChangedListener#onServiceStateChanged - * @see ServiceState - * - * @hide - */ - @SystemApi - public static final int EVENT_SERVICE_STATE_CHANGED = 1; - - /** - * Event for changes to the network signal strength (cellular). - * - * @see SignalStrengthsChangedListener#onSignalStrengthsChanged - * - * @hide - */ - @SystemApi - public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; - - /** - * Event for changes to the message-waiting indicator. - * - * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that - * the calling app has carrier privileges (see - * {@link TelephonyManager#hasCarrierPrivileges}). - * <p> - * Example: The status bar uses this to determine when to display the - * voicemail icon. - * - * @see MessageWaitingIndicatorChangedListener#onMessageWaitingIndicatorChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; - - /** - * Event for changes to the call-forwarding indicator. - * - * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that - * the calling app has carrier privileges (see - * {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see CallForwardingIndicatorChangedListener#onCallForwardingIndicatorChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; - - /** - * Event for changes to the device's cell location. Note that - * this will result in frequent callbacks to the listener. - * - * If you need regular location updates but want more control over - * the update interval or location precision, you can set up a listener - * through the {@link android.location.LocationManager location manager} - * instead. - * - * @see CellLocationChangedListener#onCellLocationChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) - public static final int EVENT_CELL_LOCATION_CHANGED = 5; - - /** - * Event for changes to the device call state. - * - * @see CallStateChangedListener#onCallStateChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) - public static final int EVENT_CALL_STATE_CHANGED = 6; - - /** - * Event for changes to the data connection state (cellular). - * - * @see DataConnectionStateChangedListener#onDataConnectionStateChanged - * - * @hide - */ - @SystemApi - public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7; - - /** - * Event for changes to the direction of data traffic on the data - * connection (cellular). - * - * Example: The status bar uses this to display the appropriate - * data-traffic icon. - * - * @see DataActivityListener#onDataActivity - * - * @hide - */ - @SystemApi - public static final int EVENT_DATA_ACTIVITY_CHANGED = 8; - - /** - * Event for changes to the network signal strengths (cellular). - * <p> - * Example: The status bar uses this to control the signal-strength - * icon. - * - * @see SignalStrengthsChangedListener#onSignalStrengthsChanged - * - * @hide - */ - @SystemApi - public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; - - /** - * Event for changes of the network signal strengths (cellular) always reported from modem, - * even in some situations such as the screen of the device is off. - * - * @see AlwaysReportedSignalStrengthChangedListener#onSignalStrengthsChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) - public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10; - - /** - * Event for changes to observed cell info. - * - * @see CellInfoChangedListener#onCellInfoChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) - public static final int EVENT_CELL_INFO_CHANGED = 11; - - /** - * Event for {@link android.telephony.Annotation.PreciseCallStates} of ringing, - * background and foreground calls. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see PreciseCallStateChangedListener#onPreciseCallStateChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12; - - /** - * Event for {@link PreciseDataConnectionState} on the data connection (cellular). - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see PreciseDataConnectionStateChangedListener#onPreciseDataConnectionStateChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; - - /** - * Event for real time info for all data connections (cellular)). - * - * @see #onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo) - * - * @deprecated Use {@link TelephonyManager#requestModemActivityInfo} - * @hide - */ - @Deprecated - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14; - - /** - * Event for OEM hook raw event - * - * @see #onOemHookRawEvent - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public static final int EVENT_OEM_HOOK_RAW = 15; - - /** - * Event for changes to the SRVCC state of the active call. - * - * @see SrvccStateChangedListener#onSrvccStateChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public static final int EVENT_SRVCC_STATE_CHANGED = 16; - - /** - * Event for carrier network changes indicated by a carrier app. - * - * @see android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean) - * @see CarrierNetworkChangeListener#onCarrierNetworkChange - * - * @hide - */ - @SystemApi - public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; - - /** - * Event for changes to the sim voice activation state - * - * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING - * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED - * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED - * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED - * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN - * - * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been - * fully activated - * - * @see VoiceActivationStateChangedListener#onVoiceActivationStateChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; - - /** - * Event for changes to the sim data activation state - * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING - * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED - * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED - * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED - * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN - * - * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been - * fully activated - * - * @see DataActivationStateChangedListener#onDataActivationStateChanged - * @hide - */ - @SystemApi - public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; - - /** - * Event for changes to the user mobile data state - * - * @see UserMobileDataStateChangedListener#onUserMobileDataStateChanged - * - * @hide - */ - @SystemApi - public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; - - /** - * Event for display info changed event. - * - * @see DisplayInfoChangedListener#onDisplayInfoChanged - * - * @hide - */ - @SystemApi - public static final int EVENT_DISPLAY_INFO_CHANGED = 21; - - /** - * Event for changes to the phone capability. - * - * @see PhoneCapabilityChangedListener#onPhoneCapabilityChanged - * - * @hide - */ - @SystemApi - public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22; - - /** - * Event for changes to active data subscription ID. Active data subscription is - * the current subscription used to setup Cellular Internet data. For example, - * it could be the current active opportunistic subscription in use, or the - * subscription user selected as default data subscription in DSDS mode. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling - * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see ActiveDataSubscriptionIdChangedListener#onActiveDataSubscriptionIdChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23; - - /** - * Event for changes to the radio power state. - * - * @see RadioPowerStateChangedListener#onRadioPowerStateChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; - - /** - * Event for changes to emergency number 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}). - * - * @see EmergencyNumberListChangedListener#onEmergencyNumberListChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public static final int EVENT_EMERGENCY_NUMBER_LIST_CHANGED = 25; - - /** - * Event for call disconnect causes which contains {@link DisconnectCause} and - * {@link PreciseDisconnectCause}. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see CallDisconnectCauseChangedListener#onCallDisconnectCauseChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26; - - /** - * Event for changes to the call attributes of a currently active call. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see CallAttributesChangedListener#onCallAttributesChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27; - - /** - * Event for IMS call disconnect causes which contains - * {@link android.telephony.ims.ImsReasonInfo} - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see ImsCallDisconnectCauseChangedListener#onImsCallDisconnectCauseChanged(ImsReasonInfo) - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; - - /** - * Event for the emergency number placed from an outgoing call. - * - * @see OutgoingEmergencyCallListener#onOutgoingEmergencyCall - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) - public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; - - /** - * Event for the emergency number placed from an outgoing SMS. - * - * @see OutgoingEmergencySmsListener#onOutgoingEmergencySms - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) - public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30; - - /** - * Event for registration failures. - * - * Event for indications that a registration procedure has failed in either the CS or PS - * domain. This indication does not necessarily indicate a change of service state, which should - * be tracked via {@link #EVENT_SERVICE_STATE_CHANGED}. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or - * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless - * of whether the calling app has carrier privileges. - * - * @see RegistrationFailedListener#onRegistrationFailed - * - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - Manifest.permission.READ_PRECISE_PHONE_STATE, - Manifest.permission.ACCESS_FINE_LOCATION - }) - public static final int EVENT_REGISTRATION_FAILURE = 31; - - /** - * Event for Barring Information for the current registered / camped cell. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or - * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless - * of whether the calling app has carrier privileges. - * - * @see BarringInfoChangedListener#onBarringInfoChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - Manifest.permission.READ_PRECISE_PHONE_STATE, - Manifest.permission.ACCESS_FINE_LOCATION - }) - public static final int EVENT_BARRING_INFO_CHANGED = 32; - - /** - * Event for changes to the physical channel configuration. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see PhysicalChannelConfigChangedListener#onPhysicalChannelConfigChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33; - - - /** - * Event for changes to the data enabled. - * - * Event for indications that the enabled status of current data has changed. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see DataEnabledChangedListener#onDataEnabledChanged - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) - public static final int EVENT_DATA_ENABLED_CHANGED = 34; - - /** - * 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 AllowedNetworkTypesChangedListener#onAllowedNetworkTypesChanged - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public static final int EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED = 35; - - /** @hide */ - @IntDef(prefix = { "EVENT_" }, value = { - EVENT_SERVICE_STATE_CHANGED, - EVENT_SIGNAL_STRENGTH_CHANGED, - EVENT_MESSAGE_WAITING_INDICATOR_CHANGED, - EVENT_CALL_FORWARDING_INDICATOR_CHANGED, - EVENT_CELL_LOCATION_CHANGED, - EVENT_CALL_STATE_CHANGED, - EVENT_DATA_CONNECTION_STATE_CHANGED, - EVENT_DATA_ACTIVITY_CHANGED, - EVENT_SIGNAL_STRENGTHS_CHANGED, - EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED, - EVENT_CELL_INFO_CHANGED, - EVENT_PRECISE_CALL_STATE_CHANGED, - EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED, - EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED, - EVENT_OEM_HOOK_RAW, - EVENT_SRVCC_STATE_CHANGED, - EVENT_CARRIER_NETWORK_CHANGED, - EVENT_VOICE_ACTIVATION_STATE_CHANGED, - EVENT_DATA_ACTIVATION_STATE_CHANGED, - EVENT_USER_MOBILE_DATA_STATE_CHANGED, - EVENT_DISPLAY_INFO_CHANGED, - EVENT_PHONE_CAPABILITY_CHANGED, - EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED, - EVENT_RADIO_POWER_STATE_CHANGED, - EVENT_EMERGENCY_NUMBER_LIST_CHANGED, - EVENT_CALL_DISCONNECT_CAUSE_CHANGED, - EVENT_CALL_ATTRIBUTES_CHANGED, - EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED, - EVENT_OUTGOING_EMERGENCY_CALL, - EVENT_OUTGOING_EMERGENCY_SMS, - EVENT_REGISTRATION_FAILURE, - EVENT_BARRING_INFO_CHANGED, - EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED, - EVENT_DATA_ENABLED_CHANGED, - EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TelephonyEvent {} - /* * Subscription used to listen to the phone state changes * @hide @@ -1085,19 +515,16 @@ public class PhoneStateListener { /** * @hide */ - //TODO: The maxTargetSdk should be S if the build time tool updates it. @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @UnsupportedAppUsage( maxTargetSdk = Build.VERSION_CODES.R, - publicAlternatives = "Use {@code TelephonyManager#registerPhoneStateListener(" + - "Executor, PhoneStateListener)} instead") - public IPhoneStateListener callback; + publicAlternatives = "Use {@code TelephonyManager#registerTelephonyCallback(" + + "Executor, TelephonyCallback)} instead") + public final IPhoneStateListener callback; /** * Create a PhoneStateListener for the Phone with the default subscription. - * If this is created for use with deprecated API - * {@link TelephonyManager#listen(PhoneStateListener, int)}, then this class requires - * Looper.myLooper() not return null. + * This class requires Looper.myLooper() not return null. */ public PhoneStateListener() { this(null, Looper.myLooper()); @@ -1135,10 +562,7 @@ public class PhoneStateListener { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public PhoneStateListener(Integer subId, Looper looper) { - if (looper != null) { - setExecutor(new HandlerExecutor(new Handler(looper))); - } - mSubId = subId; + this(subId, new HandlerExecutor(new Handler(looper))); if (subId != null && VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) { throw new IllegalArgumentException("PhoneStateListener with subId: " @@ -1153,783 +577,18 @@ public class PhoneStateListener { * The Executor must not be null. * * @param executor a non-null Executor that will execute callbacks for the PhoneStateListener. - * @deprecated Use - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)} instead. */ @Deprecated public PhoneStateListener(@NonNull Executor executor) { - setExecutor(executor); - mSubId = null; + this(null, executor); } - private @NonNull Executor mExecutor; - - /** - * @hide - */ - public void setExecutor(@NonNull @CallbackExecutor Executor executor) { - if (executor == null) { + private PhoneStateListener(Integer subId, Executor e) { + if (e == null) { throw new IllegalArgumentException("PhoneStateListener Executor must be non-null"); } - mExecutor = executor; - callback = new IPhoneStateListenerStub(this, mExecutor); - } - - /** - * @hide - */ - public boolean isExecutorSet() { - return mExecutor != null; - } - - /** - * Interface for service state listener. - */ - public interface ServiceStateChangedListener { - /** - * Callback invoked when device service state changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * The instance of {@link ServiceState} passed as an argument here will have various - * levels of location information stripped from it depending on the location permissions - * that your app holds. - * Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will - * receive all the information in {@link ServiceState}. - * - * @see ServiceState#STATE_EMERGENCY_ONLY - * @see ServiceState#STATE_IN_SERVICE - * @see ServiceState#STATE_OUT_OF_SERVICE - * @see ServiceState#STATE_POWER_OFF - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void onServiceStateChanged(@NonNull ServiceState serviceState); - } - - /** - * Interface for message waiting indicator listener. - */ - public interface MessageWaitingIndicatorChangedListener { - /** - * Callback invoked when the message-waiting indicator changes on the registered - * subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public void onMessageWaitingIndicatorChanged(boolean mwi); - } - - /** - * Interface for call-forwarding indicator listener. - */ - public interface CallForwardingIndicatorChangedListener { - /** - * Callback invoked when the call-forwarding indicator changes on the registered - * subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public void onCallForwardingIndicatorChanged(boolean cfi); - } - - /** - * Interface for device cell location listener. - */ - public interface CellLocationChangedListener { - /** - * Callback invoked when device cell location changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - */ - @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) - public void onCellLocationChanged(@NonNull CellLocation location); - } - - /** - * Interface for call state listener. - */ - public interface CallStateChangedListener { - /** - * Callback invoked when device call state changes. - * <p> - * Reports the state of Telephony (mobile) calls on the device for the registered s - * ubscription. - * <p> - * Note: the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * <p> - * Note: The state returned here may differ from that returned by - * {@link TelephonyManager#getCallState()}. Receivers of this callback should be aware that - * calling {@link TelephonyManager#getCallState()} from within this callback may return a - * different state than the callback reports. - * - * @param state call state - * @param phoneNumber call phone number. If application does not have - * {@link android.Manifest.permission#READ_CALL_LOG} permission or carrier - * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), an empty string will be - * passed as an argument. - */ - @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) - public void onCallStateChanged(@CallState int state, @Nullable String phoneNumber); - } - - /** - * Interface for data connection state listener. - */ - public interface DataConnectionStateChangedListener { - /** - * Callback invoked when connection state changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @see TelephonyManager#DATA_DISCONNECTED - * @see TelephonyManager#DATA_CONNECTING - * @see TelephonyManager#DATA_CONNECTED - * @see TelephonyManager#DATA_SUSPENDED - * - * @param state is the current state of data connection. - * @param networkType is the current network type of data connection. - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void onDataConnectionStateChanged(@DataState int state, - @NetworkType int networkType); - } - - /** - * Interface for data activity state listener. - */ - public interface DataActivityListener { - /** - * Callback invoked when data activity state changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @see TelephonyManager#DATA_ACTIVITY_NONE - * @see TelephonyManager#DATA_ACTIVITY_IN - * @see TelephonyManager#DATA_ACTIVITY_OUT - * @see TelephonyManager#DATA_ACTIVITY_INOUT - * @see TelephonyManager#DATA_ACTIVITY_DORMANT - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void onDataActivity(@DataActivityType int direction); - } - - /** - * Interface for network signal strengths listener. - */ - public interface SignalStrengthsChangedListener { - /** - * Callback invoked when network signal strengths changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength); - } - - /** - * Interface for network signal strengths listener which always reported from modem. - */ - public interface AlwaysReportedSignalStrengthChangedListener { - /** - * Callback always invoked from modem when network signal strengths changes on the - * registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - */ - @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) - public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength); - } - - /** - * Interface for cell info listener. - */ - public interface CellInfoChangedListener { - /** - * Callback invoked when a observed cell info has changed or new cells have been added - * or removed on the registered subscription. - * Note, the registration subscription ID s from {@link TelephonyManager} object - * which registersPhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param cellInfo is the list of currently visible cells. - */ - @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) - public void onCellInfoChanged(@NonNull List<CellInfo> cellInfo); - } - - /** - * Interface for precise device call state listener. - * - * @hide - */ - @SystemApi - public interface PreciseCallStateChangedListener { - /** - * Callback invoked when precise device call state changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param callState {@link PreciseCallState} - */ - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public void onPreciseCallStateChanged(@NonNull PreciseCallState callState); - } - - /** - * Interface for call disconnect cause listener. - */ - public interface CallDisconnectCauseChangedListener { - /** - * Callback invoked when call disconnect cause changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param disconnectCause {@link DisconnectCause}. - * @param preciseDisconnectCause {@link PreciseDisconnectCause}. - */ - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public void onCallDisconnectCauseChanged(@DisconnectCauses int disconnectCause, - @PreciseDisconnectCauses int preciseDisconnectCause); - } - - /** - * Interface for IMS call disconnect cause listener. - */ - public interface ImsCallDisconnectCauseChangedListener { - /** - * Callback invoked when IMS call disconnect cause changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed. - * - */ - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo); - } - - /** - * Interface for precise data connection state listener. - */ - public interface PreciseDataConnectionStateChangedListener { - /** - * Callback providing update about the default/internet data connection on the registered - * subscription. - * - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} - * or the calling app has carrier privileges - * (see {@link TelephonyManager#hasCarrierPrivileges}). - * - * @param dataConnectionState {@link PreciseDataConnectionState} - */ - @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) - public void onPreciseDataConnectionStateChanged( - @NonNull PreciseDataConnectionState dataConnectionState); - } - - /** - * Interface for Single Radio Voice Call Continuity listener. - * - * @hide - */ - @SystemApi - public interface SrvccStateChangedListener { - /** - * Callback invoked when there has been a change in the Single Radio Voice Call Continuity - * (SRVCC) state for the currently active call on the registered subscription. - * - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - */ - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void onSrvccStateChanged(@SrvccState int srvccState); - } - - /** - * Interface for SIM voice activation state listener. - * - * @hide - */ - @SystemApi - public interface VoiceActivationStateChangedListener { - /** - * Callback invoked when the SIM voice activation state has changed on the registered - * subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param state is the current SIM voice activation state - */ - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void onVoiceActivationStateChanged(@SimActivationState int state); - - } - - /** - * Interface for SIM data activation state listener. - */ - public interface DataActivationStateChangedListener { - /** - * Callback invoked when the SIM data activation state has changed on the registered - * subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param state is the current SIM data activation state - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void onDataActivationStateChanged(@SimActivationState int state); - } - - /** - * Interface for user mobile data state listener. - */ - public interface UserMobileDataStateChangedListener { - /** - * Callback invoked when the user mobile data state has changed on the registered - * subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param enabled indicates whether the current user mobile data state is enabled or - * disabled. - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void onUserMobileDataStateChanged(boolean enabled); - } - - /** - * Interface for display info listener. - */ - public interface DisplayInfoChangedListener { - /** - * Callback invoked when the display info has changed on the registered subscription. - * <p> The {@link TelephonyDisplayInfo} contains status information shown to the user - * based on carrier policy. - * - * @param telephonyDisplayInfo The display information. - */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo); - } - - /** - * Interface for the current emergency number list listener. - */ - public interface EmergencyNumberListChangedListener { - /** - * Callback invoked when the current emergency number list has changed on the registered - * subscription. - * - * Note, the registered subscription is associated with {@link TelephonyManager} object - * on which - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)} - * 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 emergencyNumberList Map associating all active subscriptions on the device with - * the list of emergency numbers originating from that - * subscription. - * If there are no active subscriptions, the map will contain a - * single entry with - * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} as - * the key and a list of emergency numbers as the value. If no - * emergency number information is available, the value will be - * empty. - */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public void onEmergencyNumberListChanged( - @NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList); - } - - /** - * Interface for outgoing emergency call listener. - * - * @hide - */ - @SystemApi - public interface OutgoingEmergencyCallListener { - /** - * Callback invoked when an outgoing call is placed to an emergency number. - * - * This method will be called when an emergency call is placed on any subscription - * (including the no-SIM case), regardless of which subscription this listener was - * registered on. - * - * The default implementation of this method calls - * {@link #onOutgoingEmergencyCall(EmergencyNumber)} for backwards compatibility purposes. - * Do not call {@code super(...)} from within your implementation unless you want - * {@link #onOutgoingEmergencyCall(EmergencyNumber)} to be called as well. - * - * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was - * placed to. - * @param subscriptionId The subscription ID used to place the emergency call. If the - * emergency call was placed without a valid subscription - * (e.g. when there are no SIM cards in the device), this will be - * equal to {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. - */ - @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) - public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber, - int subscriptionId); - } - - /** - * Interface for outgoing emergency sms listener. - * - * @hide - */ - @SystemApi - public interface OutgoingEmergencySmsListener { - /** - * Smsback invoked when an outgoing sms is sent to an emergency number. - * - * This method will be called when an emergency sms is sent on any subscription, - * regardless of which subscription this listener was registered on. - * - * The default implementation of this method calls - * {@link #onOutgoingEmergencySms(EmergencyNumber)} for backwards compatibility purposes. Do - * not call {@code super(...)} from within your implementation unless you want - * {@link #onOutgoingEmergencySms(EmergencyNumber)} to be called as well. - * - * @param sentEmergencyNumber The {@link EmergencyNumber} the emergency sms was sent to. - * @param subscriptionId The subscription ID used to send the emergency sms. - */ - @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) - public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber, - int subscriptionId); - } - - /** - * Interface for phone capability listener. - * @hide - */ - @SystemApi - public interface PhoneCapabilityChangedListener { - /** - * Callback invoked when phone capability changes. - * Note, this callback triggers regardless of registered subscription. - * - * @param capability the new phone capability - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void onPhoneCapabilityChanged(@NonNull PhoneCapability capability); - } - - /** - * Interface for active data subscription ID listener. - */ - public interface ActiveDataSubscriptionIdChangedListener { - /** - * Callback invoked when active data subscription ID changes. - * Note, this callback triggers regardless of registered subscription. - * - * @param subId current subscription used to setup Cellular Internet data. - * For example, it could be the current active opportunistic subscription - * in use, or the subscription user selected as default data subscription in - * DSDS mode. - */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public void onActiveDataSubscriptionIdChanged(int subId); - } - - /** - * Interface for modem radio power state listener. - * - * @hide - */ - @SystemApi - public interface RadioPowerStateChangedListener { - /** - * Callback invoked when modem radio power state changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param state the modem radio power state - */ - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void onRadioPowerStateChanged(@RadioPowerState int state); - } - - /** - * Interface for carrier network listener. - */ - public interface CarrierNetworkChangeListener { - /** - * Callback invoked when telephony has received notice from a carrier - * app that a network action that could result in connectivity loss - * has been requested by an app using - * {@link android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)} - * - * This is optional and is only used to allow the system to provide alternative UI while - * telephony is performing an action that may result in intentional, temporary network - * lack of connectivity. - * - * Note, this callback is pinned to the registered subscription and will be invoked when - * the notifying carrier app has carrier privilege rule on the registered - * subscription. {@link android.telephony.TelephonyManager#hasCarrierPrivileges} - * - * @param active If the carrier network change is or shortly will be active, - * {@code true} indicate that showing alternative UI, {@code false} otherwise. - */ - public void onCarrierNetworkChange(boolean active); - } - - /** - * Interface for registration failures listener. - */ - public interface RegistrationFailedListener { - /** - * Report that Registration or a Location/Routing/Tracking Area update has failed. - * - * <p>Indicate whenever a registration procedure, including a location, routing, or tracking - * area update fails. This includes procedures that do not necessarily result in a change of - * the modem's registration status. If the modem's registration status changes, that is - * reflected in the onNetworkStateChanged() and subsequent - * get{Voice/Data}RegistrationState(). - * - * <p>Because registration failures are ephemeral, this callback is not sticky. - * Registrants will not receive the most recent past value when registering. - * - * @param cellIdentity the CellIdentity, which must include the globally unique identifier - * for the cell (for example, all components of the CGI or ECGI). - * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those broadcast by the - * cell that was chosen for the failed registration attempt. - * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure. - * @param causeCode the primary failure cause code of the procedure. - * For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95 - * For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147 - * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 - * For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2 - * Integer.MAX_VALUE if this value is unused. - * @param additionalCauseCode the cause code of any secondary/combined procedure - * if appropriate. For UMTS, if a combined attach succeeds for - * PS only, then the GMM cause code shall be included as an - * additionalCauseCode. For LTE (ESM), cause codes are in - * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused. - */ - @RequiresPermission(allOf = { - Manifest.permission.READ_PRECISE_PHONE_STATE, - Manifest.permission.ACCESS_FINE_LOCATION - }) - public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, - @NonNull String chosenPlmn, @Domain int domain, - int causeCode, int additionalCauseCode); - } - - /** - * Interface for the current allowed network type list listener. This list involves values of - * allowed network type for each of reasons. - * - * @hide - */ - @SystemApi - public interface AllowedNetworkTypesChangedListener { - /** - * Callback invoked when the current allowed network type list has changed on the - * registered subscription. - * Note, the registered subscription is associated with {@link TelephonyManager} object - * on which - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)} - * 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. - * 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}} - */ - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - void onAllowedNetworkTypesChanged( - @NonNull Map<Integer, Long> allowedNetworkTypesList); - } - - /** - * Interface for call attributes listener. - * - * @hide - */ - @SystemApi - public interface CallAttributesChangedListener { - /** - * Callback invoked when the call attributes changes on the registered subscription. - * Note, the registration subscription ID comes from {@link TelephonyManager} object - * which registers PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. - * If this TelephonyManager object was created with - * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the - * subscription ID. Otherwise, this callback applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - * - * @param callAttributes the call attributes - */ - @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) - void onCallAttributesChanged(@NonNull CallAttributes callAttributes); - } - - /** - * Interface for barring information listener. - */ - public interface BarringInfoChangedListener { - /** - * Report updated barring information for the current camped/registered cell. - * - * <p>Barring info is provided for all services applicable to the current camped/registered - * cell, for the registered PLMN and current access class/access category. - * - * @param barringInfo for all services on the current cell. - * @see android.telephony.BarringInfo - */ - @RequiresPermission(allOf = { - Manifest.permission.READ_PRECISE_PHONE_STATE, - Manifest.permission.ACCESS_FINE_LOCATION - }) - public void onBarringInfoChanged(@NonNull BarringInfo barringInfo); - } - - /** - * Interface for current physical channel configuration listener. - * @hide - */ - @SystemApi - public interface PhysicalChannelConfigChangedListener { - /** - * Callback invoked when the current physical channel configuration has changed - * - * @param configs List of the current {@link PhysicalChannelConfig}s - */ - @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) - public void onPhysicalChannelConfigChanged(@NonNull List<PhysicalChannelConfig> configs); - } - - /** - * Interface for data enabled listener. - * - * @hide - */ - @SystemApi - public interface DataEnabledChangedListener { - /** - * Callback invoked when the data enabled changes. - * - * @param enabled {@code true} if data is enabled, otherwise disabled. - * @param reason Reason for data enabled/disabled. - * See {@link TelephonyManager.DataEnabledReason}. - */ - @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) - public void onDataEnabledChanged(boolean enabled, - @DataEnabledReason int reason); + mSubId = subId; + callback = new IPhoneStateListenerStub(this, e); } /** @@ -1950,7 +609,9 @@ public class PhoneStateListener { * @see ServiceState#STATE_IN_SERVICE * @see ServiceState#STATE_OUT_OF_SERVICE * @see ServiceState#STATE_POWER_OFF + * @deprecated Use {@link TelephonyCallback.ServiceStateListener} instead. */ + @Deprecated public void onServiceStateChanged(ServiceState serviceState) { // default implementation empty } @@ -1983,7 +644,10 @@ public class PhoneStateListener { * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @deprecated Use {@link TelephonyCallback.MessageWaitingIndicatorListener} instead. */ + @Deprecated public void onMessageWaitingIndicatorChanged(boolean mwi) { // default implementation empty } @@ -1996,7 +660,10 @@ public class PhoneStateListener { * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @deprecated Use {@link TelephonyCallback.CallForwardingIndicatorListener} instead. */ + @Deprecated public void onCallForwardingIndicatorChanged(boolean cfi) { // default implementation empty } @@ -2009,7 +676,10 @@ public class PhoneStateListener { * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @deprecated Use {@link TelephonyCallback.CellLocationListener} instead. */ + @Deprecated public void onCellLocationChanged(CellLocation location) { // default implementation empty } @@ -2036,7 +706,10 @@ public class PhoneStateListener { * {@link android.Manifest.permission#READ_CALL_LOG READ_CALL_LOG} permission or carrier * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), an empty string will be * passed as an argument. + * + * @deprecated Use {@link TelephonyCallback.CallStateListener} instead. */ + @Deprecated public void onCallStateChanged(@CallState int state, String phoneNumber) { // default implementation empty } @@ -2054,14 +727,19 @@ public class PhoneStateListener { * @see TelephonyManager#DATA_CONNECTING * @see TelephonyManager#DATA_CONNECTED * @see TelephonyManager#DATA_SUSPENDED + * @deprecated Use {@link TelephonyCallback.DataConnectionStateListener} instead. */ + @Deprecated public void onDataConnectionStateChanged(int state) { // default implementation empty } /** * same as above, but with the network type. Both called. + * + * @deprecated Use {@link TelephonyCallback.DataConnectionStateListener} instead. */ + @Deprecated public void onDataConnectionStateChanged(int state, int networkType) { // default implementation empty } @@ -2080,7 +758,9 @@ public class PhoneStateListener { * @see TelephonyManager#DATA_ACTIVITY_OUT * @see TelephonyManager#DATA_ACTIVITY_INOUT * @see TelephonyManager#DATA_ACTIVITY_DORMANT + * @deprecated Use {@link TelephonyCallback.DataActivityListener} instead. */ + @Deprecated public void onDataActivity(int direction) { // default implementation empty } @@ -2093,7 +773,10 @@ public class PhoneStateListener { * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @deprecated Use {@link TelephonyCallback.SignalStrengthsListener} instead. */ + @Deprecated public void onSignalStrengthsChanged(SignalStrength signalStrength) { // default implementation empty } @@ -2109,7 +792,9 @@ public class PhoneStateListener { * {@link SubscriptionManager#getDefaultSubscriptionId()}. * * @param cellInfo is the list of currently visible cells. + * @deprecated Use {@link TelephonyCallback.CellInfoListener} instead. */ + @Deprecated public void onCellInfoChanged(List<CellInfo> cellInfo) { // default implementation empty } @@ -2122,11 +807,14 @@ public class PhoneStateListener { * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * * @param callState {@link PreciseCallState} * @hide + * @deprecated Use {@link TelephonyCallback.PreciseCallStateListener} instead. */ @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @SystemApi + @Deprecated public void onPreciseCallStateChanged(@NonNull PreciseCallState callState) { // default implementation empty } @@ -2142,9 +830,10 @@ public class PhoneStateListener { * * @param disconnectCause {@link DisconnectCause}. * @param preciseDisconnectCause {@link PreciseDisconnectCause}. - * + * @deprecated Use {@link TelephonyCallback.CallDisconnectCauseListener} instead. */ @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + @Deprecated public void onCallDisconnectCauseChanged(@DisconnectCauses int disconnectCause, @PreciseDisconnectCauses int preciseDisconnectCause) { // default implementation empty @@ -2160,9 +849,10 @@ public class PhoneStateListener { * {@link SubscriptionManager#getDefaultSubscriptionId()}. * * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed. - * + * @deprecated Use {@link TelephonyCallback.ImsCallDisconnectCauseListener} instead. */ @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + @Deprecated public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo) { // default implementation empty } @@ -2183,8 +873,10 @@ public class PhoneStateListener { * (see {@link TelephonyManager#hasCarrierPrivileges}). * * @param dataConnectionState {@link PreciseDataConnectionState} + * @deprecated Use {@link TelephonyCallback.PreciseDataConnectionStateListener} instead. */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @Deprecated public void onPreciseDataConnectionStateChanged( @NonNull PreciseDataConnectionState dataConnectionState) { // default implementation empty @@ -2200,8 +892,10 @@ public class PhoneStateListener { * {@link SubscriptionManager#getDefaultSubscriptionId()}. * * @hide + * @deprecated Use {@link TelephonyManager#requestModemActivityInfo} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated public void onDataConnectionRealTimeInfoChanged( DataConnectionRealTimeInfo dcRtInfo) { // default implementation empty @@ -2219,11 +913,12 @@ public class PhoneStateListener { * {@link SubscriptionManager#getDefaultSubscriptionId()}. * * @hide + * @deprecated Use {@link TelephonyCallback.SrvccStateListener} instead. */ @SystemApi + @Deprecated public void onSrvccStateChanged(@SrvccState int srvccState) { // default implementation empty - } /** @@ -2238,8 +933,10 @@ public class PhoneStateListener { * * @param state is the current SIM voice activation state * @hide + * @deprecated Use {@link TelephonyCallback.VoiceActivationStateListener} instead. */ @SystemApi + @Deprecated public void onVoiceActivationStateChanged(@SimActivationState int state) { // default implementation empty } @@ -2256,7 +953,9 @@ public class PhoneStateListener { * * @param state is the current SIM data activation state * @hide + * @deprecated Use {@link TelephonyCallback.DataActivationStateListener} instead. */ + @Deprecated public void onDataActivationStateChanged(@SimActivationState int state) { // default implementation empty } @@ -2271,7 +970,9 @@ public class PhoneStateListener { * {@link SubscriptionManager#getDefaultSubscriptionId()}. * * @param enabled indicates whether the current user mobile data state is enabled or disabled. + * @deprecated Use {@link TelephonyCallback.UserMobileDataStateListener} instead. */ + @Deprecated public void onUserMobileDataStateChanged(boolean enabled) { // default implementation empty } @@ -2285,8 +986,10 @@ public class PhoneStateListener { * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * * @param telephonyDisplayInfo The display information. + * @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead. */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @Deprecated public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) { // default implementation empty } @@ -2309,7 +1012,9 @@ public class PhoneStateListener { * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} as * the key and a list of emergency numbers as the value. If no * emergency number information is available, the value will be null. + * @deprecated Use {@link TelephonyCallback.EmergencyNumberListListener} instead. */ + @Deprecated public void onEmergencyNumberListChanged( @NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList) { // default implementation empty @@ -2322,7 +1027,6 @@ public class PhoneStateListener { * the no-SIM case), regardless of which subscription this listener was registered on. * * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was placed to. - * * @deprecated Use {@link #onOutgoingEmergencyCall(EmergencyNumber, int)}. * @hide */ @@ -2349,8 +1053,10 @@ public class PhoneStateListener { * are no SIM cards in the device), this will be equal to * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. * @hide + * @deprecated Use {@link TelephonyCallback.OutgoingEmergencyCallListener} instead. */ @SystemApi + @Deprecated public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber, int subscriptionId) { // Default implementation for backwards compatibility @@ -2365,6 +1071,7 @@ public class PhoneStateListener { * * @deprecated Use {@link #onOutgoingEmergencySms(EmergencyNumber, int)}. * @hide + * @deprecated Use {@link TelephonyCallback.OutgoingEmergencySmsListener} instead. */ @SystemApi @Deprecated @@ -2386,8 +1093,10 @@ public class PhoneStateListener { * @param sentEmergencyNumber The {@link EmergencyNumber} the emergency sms was sent to. * @param subscriptionId The subscription ID used to send the emergency sms. * @hide + * @deprecated Use {@link TelephonyCallback.OutgoingEmergencySmsListener} instead. */ @SystemApi + @Deprecated public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber, int subscriptionId) { // Default implementation for backwards compatibility @@ -2397,8 +1106,7 @@ public class PhoneStateListener { /** * Callback invoked when OEM hook raw event is received on the registered subscription. * Note, the registration subId comes from {@link TelephonyManager} object which registers - * PhoneStateListener by - * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}. + * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}. * If this TelephonyManager object was created with * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the * subId. Otherwise, this callback applies to @@ -2407,8 +1115,10 @@ public class PhoneStateListener { * Requires the READ_PRIVILEGED_PHONE_STATE permission. * @param rawData is the byte array of the OEM hook raw data. * @hide + * @deprecated OEM needs a vendor-extension hal and their apps should use that instead */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated public void onOemHookRawEvent(byte[] rawData) { // default implementation empty } @@ -2419,7 +1129,9 @@ public class PhoneStateListener { * * @param capability the new phone capability * @hide + * @deprecated Use {@link TelephonyCallback.PhoneCapabilityListener} instead. */ + @Deprecated public void onPhoneCapabilityChanged(@NonNull PhoneCapability capability) { // default implementation empty } @@ -2432,7 +1144,9 @@ public class PhoneStateListener { * @param subId current subscription used to setup Cellular Internet data. * For example, it could be the current active opportunistic subscription in use, * or the subscription user selected as default data subscription in DSDS mode. + * @deprecated Use {@link TelephonyCallback.ActiveDataSubscriptionIdListener} instead. */ + @Deprecated public void onActiveDataSubscriptionIdChanged(int subId) { // default implementation empty } @@ -2449,8 +1163,10 @@ public class PhoneStateListener { * Requires the READ_PRECISE_PHONE_STATE permission. * @param callAttributes the call attributes * @hide + * @deprecated Use {@link TelephonyCallback.CallAttributesListener} instead. */ @SystemApi + @Deprecated public void onCallAttributesChanged(@NonNull CallAttributes callAttributes) { // default implementation empty } @@ -2468,8 +1184,10 @@ public class PhoneStateListener { * * @param state the modem radio power state * @hide + * @deprecated Use {@link TelephonyCallback.RadioPowerStateListener} instead. */ @SystemApi + @Deprecated public void onRadioPowerStateChanged(@RadioPowerState int state) { // default implementation empty } @@ -2487,9 +1205,10 @@ public class PhoneStateListener { * @param active Whether the carrier network change is or shortly * will be active. This value is true to indicate * showing alternative UI and false to stop. - * * @hide + * @deprecated Use {@link TelephonyCallback.CarrierNetworkListener} instead. */ + @Deprecated public void onCarrierNetworkChange(boolean active) { // default implementation empty } @@ -2520,7 +1239,9 @@ public class PhoneStateListener { * For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be * included as an additionalCauseCode. For LTE (ESM), cause codes are in * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused. + * @deprecated Use {@link TelephonyCallback.RegistrationFailedListener} instead. */ + @Deprecated public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) { // default implementation empty @@ -2533,9 +1254,10 @@ public class PhoneStateListener { * cell, for the registered PLMN and current access class/access category. * * @param barringInfo for all services on the current cell. - * * @see android.telephony.BarringInfo + * @deprecated Use {@link TelephonyCallback.BarringInfoListener} instead. */ + @Deprecated public void onBarringInfoChanged(@NonNull BarringInfo barringInfo) { // default implementation empty } @@ -2831,7 +1553,7 @@ public class PhoneStateListener { Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> psl.onRegistrationFailed( - cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode))); + cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode))); // default implementation empty } @@ -2844,33 +1566,15 @@ public class PhoneStateListener { } public void onPhysicalChannelConfigChanged(List<PhysicalChannelConfig> configs) { - PhysicalChannelConfigChangedListener listener = - (PhysicalChannelConfigChangedListener) mPhoneStateListenerWeakRef.get(); - if (listener == null) return; - - Binder.withCleanCallingIdentity( - () -> mExecutor.execute(() -> listener.onPhysicalChannelConfigChanged( - configs))); + // default implementation empty } public void onDataEnabledChanged(boolean enabled, @DataEnabledReason int reason) { - DataEnabledChangedListener listener = - (DataEnabledChangedListener) mPhoneStateListenerWeakRef.get(); - if (listener == null) return; - - Binder.withCleanCallingIdentity( - () -> mExecutor.execute(() -> listener.onDataEnabledChanged( - enabled, reason))); + // default implementation empty } public void onAllowedNetworkTypesChanged(Map allowedNetworkTypesList) { - AllowedNetworkTypesChangedListener listener = - (AllowedNetworkTypesChangedListener) mPhoneStateListenerWeakRef.get(); - if (listener == null) return; - - Binder.withCleanCallingIdentity( - () -> mExecutor.execute( - () -> listener.onAllowedNetworkTypesChanged(allowedNetworkTypesList))); + // default implementation empty } } diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java new file mode 100644 index 000000000000..a2584cae1b9c --- /dev/null +++ b/core/java/android/telephony/TelephonyCallback.java @@ -0,0 +1,1710 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +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; +import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.IPhoneStateListener; + +import dalvik.system.VMRuntime; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * A callback class for monitoring changes in specific telephony states + * on the device, including service state, signal strength, message + * waiting indicator (voicemail), and others. + * <p> + * To register a callback, use a {@link TelephonyCallback} which implements interfaces regarding + * EVENT_*. For example, + * FakeServiceStateCallback extends {@link TelephonyCallback} implements + * {@link TelephonyCallback.ServiceStateListener}. + * <p> + * Then override the methods for the state that you wish to receive updates for, and + * pass the executor and your TelephonyCallback object to + * {@link TelephonyManager#registerTelephonyCallback}. + * Methods are called when the state changes, as well as once on initial registration. + * <p> + * Note that access to some telephony information is + * permission-protected. Your application won't receive updates for protected + * information unless it has the appropriate permissions declared in + * its manifest file. Where permissions apply, they are noted in the + * appropriate sub-interfaces. + */ +public class TelephonyCallback { + + /** + * Experiment flag to set the per-pid registration limit for TelephonyCallback + * + * Limit on registrations of {@link TelephonyCallback}s on a per-pid basis. When this limit is + * exceeded, any calls to {@link TelephonyManager#registerTelephonyCallback} will fail with an + * {@link IllegalStateException}. + * + * {@link android.os.Process#PHONE_UID}, {@link android.os.Process#SYSTEM_UID}, and the uid that + * TelephonyRegistry runs under are exempt from this limit. + * + * If the value of the flag is less than 1, enforcement of the limit will be disabled. + * @hide + */ + public static final String FLAG_PER_PID_REGISTRATION_LIMIT = + "phone_state_listener_per_pid_registration_limit"; + + /** + * Default value for the per-pid registration limit. + * See {@link #FLAG_PER_PID_REGISTRATION_LIMIT}. + * @hide + */ + public static final int DEFAULT_PER_PID_REGISTRATION_LIMIT = 50; + + /** + * This change enables a limit on the number of {@link TelephonyCallback} objects any process + * may register via {@link TelephonyManager#registerTelephonyCallback}. The default limit is 50, + * which may change via remote device config updates. + * + * This limit is enforced via an {@link IllegalStateException} thrown from + * {@link TelephonyManager#registerTelephonyCallback} when the offending process attempts to + * register one too many callbacks. + * + * @hide + */ + @ChangeId + public static final long PHONE_STATE_LISTENER_LIMIT_CHANGE_ID = 150880553L; + + /** + * Event for changes to the network service state (cellular). + * + * @hide + * @see ServiceStateListener#onServiceStateChanged + * @see ServiceState + */ + @SystemApi + public static final int EVENT_SERVICE_STATE_CHANGED = 1; + + /** + * Event for changes to the network signal strength (cellular). + * + * @hide + * @see SignalStrengthsListener#onSignalStrengthsChanged + */ + @SystemApi + public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; + + /** + * Event for changes to the message-waiting indicator. + * <p> + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that + * the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * <p> + * Example: The status bar uses this to determine when to display the + * voicemail icon. + * + * @hide + * @see MessageWaitingIndicatorListener#onMessageWaitingIndicatorChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; + + /** + * Event for changes to the call-forwarding indicator. + * <p> + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that + * the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see CallForwardingIndicatorListener#onCallForwardingIndicatorChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; + + /** + * Event for changes to the device's cell location. Note that + * this will result in frequent listeners to the listener. + * <p> + * If you need regular location updates but want more control over + * the update interval or location precision, you can set up a callback + * through the {@link android.location.LocationManager location manager} + * instead. + * + * @hide + * @see CellLocationListener#onCellLocationChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public static final int EVENT_CELL_LOCATION_CHANGED = 5; + + /** + * Event for changes to the device call state. + * + * @hide + * @see CallStateListener#onCallStateChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) + public static final int EVENT_CALL_STATE_CHANGED = 6; + + /** + * Event for changes to the data connection state (cellular). + * + * @hide + * @see DataConnectionStateListener#onDataConnectionStateChanged + */ + @SystemApi + public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7; + + /** + * Event for changes to the direction of data traffic on the data + * connection (cellular). + * <p> + * Example: The status bar uses this to display the appropriate + * data-traffic icon. + * + * @hide + * @see DataActivityListener#onDataActivity + */ + @SystemApi + public static final int EVENT_DATA_ACTIVITY_CHANGED = 8; + + /** + * Event for changes to the network signal strengths (cellular). + * <p> + * Example: The status bar uses this to control the signal-strength + * icon. + * + * @hide + * @see SignalStrengthsListener#onSignalStrengthsChanged + */ + @SystemApi + public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; + + /** + * Event for changes of the network signal strengths (cellular) always reported from modem, + * even in some situations such as the screen of the device is off. + * + * @hide + * @see AlwaysReportedSignalStrengthListener#onSignalStrengthsChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) + public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10; + + /** + * Event for changes to observed cell info. + * + * @hide + * @see CellInfoListener#onCellInfoChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public static final int EVENT_CELL_INFO_CHANGED = 11; + + /** + * Event for {@link android.telephony.Annotation.PreciseCallStates} of ringing, + * background and foreground calls. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see PreciseCallStateListener#onPreciseCallStateChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12; + + /** + * Event for {@link PreciseDataConnectionState} on the data connection (cellular). + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see PreciseDataConnectionStateListener#onPreciseDataConnectionStateChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; + + /** + * Event for real time info for all data connections (cellular)). + * + * @hide + * @see PhoneStateListener#onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo) + * @deprecated Use {@link TelephonyManager#requestModemActivityInfo} + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14; + + /** + * Event for OEM hook raw event + * + * @hide + * @see PhoneStateListener#onOemHookRawEvent + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public static final int EVENT_OEM_HOOK_RAW = 15; + + /** + * Event for changes to the SRVCC state of the active call. + * + * @hide + * @see SrvccStateListener#onSrvccStateChanged + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public static final int EVENT_SRVCC_STATE_CHANGED = 16; + + /** + * Event for carrier network changes indicated by a carrier app. + * + * @hide + * @see android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean) + * @see CarrierNetworkListener#onCarrierNetworkChange + */ + @SystemApi + public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; + + /** + * Event for changes to the sim voice activation state + * + * @hide + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED + * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN + * <p> + * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been + * fully activated + * @see VoiceActivationStateListener#onVoiceActivationStateChanged + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; + + /** + * Event for changes to the sim data activation state + * + * @hide + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED + * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN + * <p> + * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been + * fully activated + * @see DataActivationStateListener#onDataActivationStateChanged + */ + @SystemApi + public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; + + /** + * Event for changes to the user mobile data state + * + * @hide + * @see UserMobileDataStateListener#onUserMobileDataStateChanged + */ + @SystemApi + public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; + + /** + * Event for display info changed event. + * + * @hide + * @see DisplayInfoListener#onDisplayInfoChanged + */ + @SystemApi + public static final int EVENT_DISPLAY_INFO_CHANGED = 21; + + /** + * Event for changes to the phone capability. + * + * @hide + * @see PhoneCapabilityListener#onPhoneCapabilityChanged + */ + @SystemApi + public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22; + + /** + * Event for changes to active data subscription ID. Active data subscription is + * the current subscription used to setup Cellular Internet data. For example, + * it could be the current active opportunistic subscription in use, or the + * subscription user selected as default data subscription in DSDS mode. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling + * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see ActiveDataSubscriptionIdListener#onActiveDataSubscriptionIdChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23; + + /** + * Event for changes to the radio power state. + * + * @hide + * @see RadioPowerStateListener#onRadioPowerStateChanged + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; + + /** + * Event for changes to emergency number 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 EmergencyNumberListListener#onEmergencyNumberListChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public static final int EVENT_EMERGENCY_NUMBER_LIST_CHANGED = 25; + + /** + * Event for call disconnect causes which contains {@link DisconnectCause} and + * {@link PreciseDisconnectCause}. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see CallDisconnectCauseListener#onCallDisconnectCauseChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26; + + /** + * Event for changes to the call attributes of a currently active call. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see CallAttributesListener#onCallAttributesChanged + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27; + + /** + * Event for IMS call disconnect causes which contains + * {@link android.telephony.ims.ImsReasonInfo} + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see ImsCallDisconnectCauseListener#onImsCallDisconnectCauseChanged(ImsReasonInfo) + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; + + /** + * Event for the emergency number placed from an outgoing call. + * + * @hide + * @see OutgoingEmergencyCallListener#onOutgoingEmergencyCall + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) + public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; + + /** + * Event for the emergency number placed from an outgoing SMS. + * + * @hide + * @see OutgoingEmergencySmsListener#onOutgoingEmergencySms + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) + public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30; + + /** + * Event for registration failures. + * <p> + * Event for indications that a registration procedure has failed in either the CS or PS + * domain. This indication does not necessarily indicate a change of service state, which should + * be tracked via {@link #EVENT_SERVICE_STATE_CHANGED}. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or + * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless + * of whether the calling app has carrier privileges. + * + * @hide + * @see RegistrationFailedListener#onRegistrationFailed + */ + @SystemApi + @RequiresPermission(allOf = { + Manifest.permission.READ_PRECISE_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public static final int EVENT_REGISTRATION_FAILURE = 31; + + /** + * Event for Barring Information for the current registered / camped cell. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or + * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless + * of whether the calling app has carrier privileges. + * + * @hide + * @see BarringInfoListener#onBarringInfoChanged + */ + @SystemApi + @RequiresPermission(allOf = { + Manifest.permission.READ_PRECISE_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public static final int EVENT_BARRING_INFO_CHANGED = 32; + + /** + * Event for changes to the physical channel configuration. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see PhysicalChannelConfigListener#onPhysicalChannelConfigChanged + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33; + + + /** + * Event for changes to the data enabled. + * <p> + * Event for indications that the enabled status of current data has changed. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @hide + * @see DataEnabledListener#onDataEnabledChanged + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_DATA_ENABLED_CHANGED = 34; + + /** + * 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 + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public static final int EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED = 35; + + /** + * @hide + */ + @IntDef(prefix = {"EVENT_"}, value = { + EVENT_SERVICE_STATE_CHANGED, + EVENT_SIGNAL_STRENGTH_CHANGED, + EVENT_MESSAGE_WAITING_INDICATOR_CHANGED, + EVENT_CALL_FORWARDING_INDICATOR_CHANGED, + EVENT_CELL_LOCATION_CHANGED, + EVENT_CALL_STATE_CHANGED, + EVENT_DATA_CONNECTION_STATE_CHANGED, + EVENT_DATA_ACTIVITY_CHANGED, + EVENT_SIGNAL_STRENGTHS_CHANGED, + EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED, + EVENT_CELL_INFO_CHANGED, + EVENT_PRECISE_CALL_STATE_CHANGED, + EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED, + EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED, + EVENT_OEM_HOOK_RAW, + EVENT_SRVCC_STATE_CHANGED, + EVENT_CARRIER_NETWORK_CHANGED, + EVENT_VOICE_ACTIVATION_STATE_CHANGED, + EVENT_DATA_ACTIVATION_STATE_CHANGED, + EVENT_USER_MOBILE_DATA_STATE_CHANGED, + EVENT_DISPLAY_INFO_CHANGED, + EVENT_PHONE_CAPABILITY_CHANGED, + EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED, + EVENT_RADIO_POWER_STATE_CHANGED, + EVENT_EMERGENCY_NUMBER_LIST_CHANGED, + EVENT_CALL_DISCONNECT_CAUSE_CHANGED, + EVENT_CALL_ATTRIBUTES_CHANGED, + EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED, + EVENT_OUTGOING_EMERGENCY_CALL, + EVENT_OUTGOING_EMERGENCY_SMS, + EVENT_REGISTRATION_FAILURE, + EVENT_BARRING_INFO_CHANGED, + EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED, + EVENT_DATA_ENABLED_CHANGED, + EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TelephonyEvent { + } + + /** + * @hide + */ + //TODO: The maxTargetSdk should be S if the build time tool updates it. + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public IPhoneStateListener callback; + + /** + * @hide + */ + public void init(@NonNull @CallbackExecutor Executor executor) { + if (executor == null) { + throw new IllegalArgumentException("TelephonyCallback Executor must be non-null"); + } + callback = new IPhoneStateListenerStub(this, executor); + } + + /** + * Interface for service state listener. + */ + public interface ServiceStateListener { + /** + * Callback invoked when device service state changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * <p> + * The instance of {@link ServiceState} passed as an argument here will have various + * levels of location information stripped from it depending on the location permissions + * that your app holds. + * Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will + * receive all the information in {@link ServiceState}. + * + * @see ServiceState#STATE_EMERGENCY_ONLY + * @see ServiceState#STATE_IN_SERVICE + * @see ServiceState#STATE_OUT_OF_SERVICE + * @see ServiceState#STATE_POWER_OFF + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void onServiceStateChanged(@NonNull ServiceState serviceState); + } + + /** + * Interface for message waiting indicator listener. + */ + public interface MessageWaitingIndicatorListener { + /** + * Callback invoked when the message-waiting indicator changes on the registered + * subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public void onMessageWaitingIndicatorChanged(boolean mwi); + } + + /** + * Interface for call-forwarding indicator listener. + */ + public interface CallForwardingIndicatorListener { + /** + * Callback invoked when the call-forwarding indicator changes on the registered + * subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public void onCallForwardingIndicatorChanged(boolean cfi); + } + + /** + * Interface for device cell location listener. + */ + public interface CellLocationListener { + /** + * Callback invoked when device cell location changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public void onCellLocationChanged(@NonNull CellLocation location); + } + + /** + * Interface for call state listener. + */ + public interface CallStateListener { + /** + * Callback invoked when device call state changes. + * <p> + * Reports the state of Telephony (mobile) calls on the device for the registered + * subscription. + * <p> + * Note: the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * <p> + * Note: The state returned here may differ from that returned by + * {@link TelephonyManager#getCallState()}. Receivers of this callback should be aware that + * calling {@link TelephonyManager#getCallState()} from within this callback may return a + * different state than the callback reports. + * + * @param state call state + * @param phoneNumber call phone number. If application does not have + * {@link android.Manifest.permission#READ_CALL_LOG} permission or + * carrier + * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), an + * empty string will be + * passed as an argument. + */ + @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) + public void onCallStateChanged(@Annotation.CallState int state, + @Nullable String phoneNumber); + } + + /** + * Interface for data connection state listener. + */ + public interface DataConnectionStateListener { + /** + * Callback invoked when connection state changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param state is the current state of data connection. + * @param networkType is the current network type of data connection. + * @see TelephonyManager#DATA_DISCONNECTED + * @see TelephonyManager#DATA_CONNECTING + * @see TelephonyManager#DATA_CONNECTED + * @see TelephonyManager#DATA_SUSPENDED + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void onDataConnectionStateChanged(@TelephonyManager.DataState int state, + @Annotation.NetworkType int networkType); + } + + /** + * Interface for data activity state listener. + */ + public interface DataActivityListener { + /** + * Callback invoked when data activity state changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @see TelephonyManager#DATA_ACTIVITY_NONE + * @see TelephonyManager#DATA_ACTIVITY_IN + * @see TelephonyManager#DATA_ACTIVITY_OUT + * @see TelephonyManager#DATA_ACTIVITY_INOUT + * @see TelephonyManager#DATA_ACTIVITY_DORMANT + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void onDataActivity(@Annotation.DataActivityType int direction); + } + + /** + * Interface for network signal strengths listener. + */ + public interface SignalStrengthsListener { + /** + * Callback invoked when network signal strengths changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength); + } + + /** + * Interface for network signal strengths callback which always reported from modem. + */ + public interface AlwaysReportedSignalStrengthListener { + /** + * Callback always invoked from modem when network signal strengths changes on the + * registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + */ + @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) + public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength); + } + + /** + * Interface for cell info listener. + */ + public interface CellInfoListener { + /** + * Callback invoked when a observed cell info has changed or new cells have been added + * or removed on the registered subscription. + * Note, the registration subscription ID s from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param cellInfo is the list of currently visible cells. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public void onCellInfoChanged(@NonNull List<CellInfo> cellInfo); + } + + /** + * Interface for precise device call state listener. + * + * @hide + */ + @SystemApi + public interface PreciseCallStateListener { + /** + * Callback invoked when precise device call state changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param callState {@link PreciseCallState} + */ + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public void onPreciseCallStateChanged(@NonNull PreciseCallState callState); + } + + /** + * Interface for call disconnect cause listener. + */ + public interface CallDisconnectCauseListener { + /** + * Callback invoked when call disconnect cause changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param disconnectCause {@link DisconnectCause}. + * @param preciseDisconnectCause {@link PreciseDisconnectCause}. + */ + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public void onCallDisconnectCauseChanged(@Annotation.DisconnectCauses int disconnectCause, + @Annotation.PreciseDisconnectCauses int preciseDisconnectCause); + } + + /** + * Interface for IMS call disconnect cause listener. + */ + public interface ImsCallDisconnectCauseListener { + /** + * Callback invoked when IMS call disconnect cause changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed. + */ + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo); + } + + /** + * Interface for precise data connection state listener. + */ + public interface PreciseDataConnectionStateListener { + /** + * Callback providing update about the default/internet data connection on the registered + * subscription. + * <p> + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * or the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @param dataConnectionState {@link PreciseDataConnectionState} + */ + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public void onPreciseDataConnectionStateChanged( + @NonNull PreciseDataConnectionState dataConnectionState); + } + + /** + * Interface for Single Radio Voice Call Continuity listener. + * + * @hide + */ + @SystemApi + public interface SrvccStateListener { + /** + * Callback invoked when there has been a change in the Single Radio Voice Call Continuity + * (SRVCC) state for the currently active call on the registered subscription. + * <p> + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void onSrvccStateChanged(@Annotation.SrvccState int srvccState); + } + + /** + * Interface for SIM voice activation state listener. + * + * @hide + */ + @SystemApi + public interface VoiceActivationStateListener { + /** + * Callback invoked when the SIM voice activation state has changed on the registered + * subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param state is the current SIM voice activation state + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void onVoiceActivationStateChanged(@Annotation.SimActivationState int state); + + } + + /** + * Interface for SIM data activation state listener. + */ + public interface DataActivationStateListener { + /** + * Callback invoked when the SIM data activation state has changed on the registered + * subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param state is the current SIM data activation state + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void onDataActivationStateChanged(@Annotation.SimActivationState int state); + } + + /** + * Interface for user mobile data state listener. + */ + public interface UserMobileDataStateListener { + /** + * Callback invoked when the user mobile data state has changed on the registered + * subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param enabled indicates whether the current user mobile data state is enabled or + * disabled. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void onUserMobileDataStateChanged(boolean enabled); + } + + /** + * Interface for display info listener. + */ + public interface DisplayInfoListener { + /** + * Callback invoked when the display info has changed on the registered subscription. + * <p> The {@link TelephonyDisplayInfo} contains status information shown to the user + * based on carrier policy. + * + * @param telephonyDisplayInfo The display information. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo); + } + + /** + * Interface for the current emergency number list listener. + */ + public interface EmergencyNumberListListener { + /** + * Callback invoked when the current emergency number list has changed on the registered + * subscription. + * <p> + * Note, the registered subscription is associated with {@link TelephonyManager} object + * 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 emergencyNumberList Map associating all active subscriptions on the device with + * the list of emergency numbers originating from that + * subscription. + * If there are no active subscriptions, the map will contain a + * single entry with + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} as + * the key and a list of emergency numbers as the value. If no + * emergency number information is available, the value will be + * empty. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public void onEmergencyNumberListChanged( + @NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList); + } + + /** + * Interface for outgoing emergency call listener. + * + * @hide + */ + @SystemApi + public interface OutgoingEmergencyCallListener { + /** + * Callback invoked when an outgoing call is placed to an emergency number. + * <p> + * This method will be called when an emergency call is placed on any subscription + * (including the no-SIM case), regardless of which subscription this callback was + * registered on. + * <p> + * + * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was + * placed to. + * @param subscriptionId The subscription ID used to place the emergency call. If the + * emergency call was placed without a valid subscription + * (e.g. when there are no SIM cards in the device), this + * will be + * equal to + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + */ + @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) + public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber, + int subscriptionId); + } + + /** + * Interface for outgoing emergency sms listener. + * + * @hide + */ + @SystemApi + public interface OutgoingEmergencySmsListener { + /** + * Smsback invoked when an outgoing sms is sent to an emergency number. + * <p> + * This method will be called when an emergency sms is sent on any subscription, + * regardless of which subscription this callback was registered on. + * + * @param sentEmergencyNumber The {@link EmergencyNumber} the emergency sms was sent to. + * @param subscriptionId The subscription ID used to send the emergency sms. + */ + @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) + public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber, + int subscriptionId); + } + + /** + * Interface for phone capability listener. + * + * @hide + */ + @SystemApi + public interface PhoneCapabilityListener { + /** + * Callback invoked when phone capability changes. + * Note, this callback triggers regardless of registered subscription. + * + * @param capability the new phone capability + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void onPhoneCapabilityChanged(@NonNull PhoneCapability capability); + } + + /** + * Interface for active data subscription ID listener. + */ + public interface ActiveDataSubscriptionIdListener { + /** + * Callback invoked when active data subscription ID changes. + * Note, this callback triggers regardless of registered subscription. + * + * @param subId current subscription used to setup Cellular Internet data. + * For example, it could be the current active opportunistic subscription + * in use, or the subscription user selected as default data subscription in + * DSDS mode. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public void onActiveDataSubscriptionIdChanged(int subId); + } + + /** + * Interface for modem radio power state listener. + * + * @hide + */ + @SystemApi + public interface RadioPowerStateListener { + /** + * Callback invoked when modem radio power state changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param state the modem radio power state + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void onRadioPowerStateChanged(@Annotation.RadioPowerState int state); + } + + /** + * Interface for carrier network listener. + */ + public interface CarrierNetworkListener { + /** + * Callback invoked when telephony has received notice from a carrier + * app that a network action that could result in connectivity loss + * has been requested by an app using + * {@link android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)} + * <p> + * This is optional and is only used to allow the system to provide alternative UI while + * telephony is performing an action that may result in intentional, temporary network + * lack of connectivity. + * <p> + * Note, this callback is pinned to the registered subscription and will be invoked when + * the notifying carrier app has carrier privilege rule on the registered + * subscription. {@link android.telephony.TelephonyManager#hasCarrierPrivileges} + * + * @param active If the carrier network change is or shortly will be active, + * {@code true} indicate that showing alternative UI, {@code false} otherwise. + */ + public void onCarrierNetworkChange(boolean active); + } + + /** + * Interface for registration failures listener. + */ + public interface RegistrationFailedListener { + /** + * Report that Registration or a Location/Routing/Tracking Area update has failed. + * + * <p>Indicate whenever a registration procedure, including a location, routing, or tracking + * area update fails. This includes procedures that do not necessarily result in a change of + * the modem's registration status. If the modem's registration status changes, that is + * reflected in the onNetworkStateChanged() and subsequent + * get{Voice/Data}RegistrationState(). + * + * <p>Because registration failures are ephemeral, this callback is not sticky. + * Registrants will not receive the most recent past value when registering. + * + * @param cellIdentity the CellIdentity, which must include the globally unique + * identifier + * for the cell (for example, all components of the CGI or ECGI). + * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those + * broadcast by the + * cell that was chosen for the failed registration attempt. + * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure. + * @param causeCode the primary failure cause code of the procedure. + * For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95 + * For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147 + * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 + * For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2 + * Integer.MAX_VALUE if this value is unused. + * @param additionalCauseCode the cause code of any secondary/combined procedure + * if appropriate. For UMTS, if a combined attach succeeds for + * PS only, then the GMM cause code shall be included as an + * additionalCauseCode. For LTE (ESM), cause codes are in + * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused. + */ + @RequiresPermission(allOf = { + Manifest.permission.READ_PRECISE_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, + @NonNull String chosenPlmn, @NetworkRegistrationInfo.Domain int domain, int causeCode, + int additionalCauseCode); + } + + /** + * Interface for the current allowed network type list listener. This list involves values of + * allowed network type for each of reasons. + * + * @hide + */ + @SystemApi + public interface AllowedNetworkTypesListener { + /** + * Callback invoked when the current allowed network type list has changed on the + * registered subscription. + * Note, the registered subscription is associated with {@link TelephonyManager} object + * 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. + * 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}} + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + void onAllowedNetworkTypesChanged(@NonNull Map<Integer, Long> allowedNetworkTypesList); + } + + /** + * Interface for call attributes listener. + * + * @hide + */ + @SystemApi + public interface CallAttributesListener { + /** + * Callback invoked when the call attributes changes on the registered subscription. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @param callAttributes the call attributes + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + void onCallAttributesChanged(@NonNull CallAttributes callAttributes); + } + + /** + * Interface for barring information listener. + */ + public interface BarringInfoListener { + /** + * Report updated barring information for the current camped/registered cell. + * + * <p>Barring info is provided for all services applicable to the current camped/registered + * cell, for the registered PLMN and current access class/access category. + * + * @param barringInfo for all services on the current cell. + * @see android.telephony.BarringInfo + */ + @RequiresPermission(allOf = { + Manifest.permission.READ_PRECISE_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public void onBarringInfoChanged(@NonNull BarringInfo barringInfo); + } + + /** + * Interface for current physical channel configuration listener. + * + * @hide + */ + @SystemApi + public interface PhysicalChannelConfigListener { + /** + * Callback invoked when the current physical channel configuration has changed + * + * @param configs List of the current {@link PhysicalChannelConfig}s + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public void onPhysicalChannelConfigChanged(@NonNull List<PhysicalChannelConfig> configs); + } + + /** + * Interface for data enabled listener. + * + * @hide + */ + @SystemApi + public interface DataEnabledListener { + /** + * Callback invoked when the data enabled changes. + * + * @param enabled {@code true} if data is enabled, otherwise disabled. + * @param reason Reason for data enabled/disabled. + * See {@link TelephonyManager.DataEnabledReason}. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public void onDataEnabledChanged(boolean enabled, + @TelephonyManager.DataEnabledReason int reason); + } + + + /** + * 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. + * <p> + * Using a static class and weak reference here to avoid memory leak caused by the + * IPhoneState.Stub callback retaining references to the outside TelephonyCallback: + * even caller has been destroyed and "un-registered" the TelephonyCallback, it is still not + * eligible for GC given the references coming from: + * Native Stack --> TelephonyCallback --> Context (Activity). + * memory of caller's context will be collected after GC from service side get triggered + */ + private static class IPhoneStateListenerStub extends IPhoneStateListener.Stub { + private WeakReference<TelephonyCallback> mTelephonyCallbackWeakRef; + private Executor mExecutor; + + IPhoneStateListenerStub(TelephonyCallback telephonyCallback, Executor executor) { + mTelephonyCallbackWeakRef = new WeakReference<TelephonyCallback>(telephonyCallback); + mExecutor = executor; + } + + public void onServiceStateChanged(ServiceState serviceState) { + ServiceStateListener listener = (ServiceStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onServiceStateChanged(serviceState))); + } + + public void onSignalStrengthChanged(int asu) { + // default implementation empty + } + + public void onMessageWaitingIndicatorChanged(boolean mwi) { + MessageWaitingIndicatorListener listener = + (MessageWaitingIndicatorListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onMessageWaitingIndicatorChanged(mwi))); + } + + public void onCallForwardingIndicatorChanged(boolean cfi) { + CallForwardingIndicatorListener listener = + (CallForwardingIndicatorListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onCallForwardingIndicatorChanged(cfi))); + } + + public void onCellLocationChanged(CellIdentity cellIdentity) { + // There is no system/public API to create an CellIdentity in system server, + // so the server pass a null to indicate an empty initial location. + CellLocation location = + cellIdentity == null ? CellLocation.getEmpty() : cellIdentity.asCellLocation(); + CellLocationListener listener = (CellLocationListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onCellLocationChanged(location))); + } + + public void onCallStateChanged(int state, String incomingNumber) { + CallStateListener listener = (CallStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onCallStateChanged(state, + incomingNumber))); + } + + public void onDataConnectionStateChanged(int state, int networkType) { + DataConnectionStateListener listener = + (DataConnectionStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + if (state == TelephonyManager.DATA_DISCONNECTING + && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) { + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> + listener.onDataConnectionStateChanged( + TelephonyManager.DATA_CONNECTED, networkType))); + } else { + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> + listener.onDataConnectionStateChanged(state, networkType))); + } + } + + public void onDataActivity(int direction) { + DataActivityListener listener = (DataActivityListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onDataActivity(direction))); + } + + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + SignalStrengthsListener listener = + (SignalStrengthsListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onSignalStrengthsChanged( + signalStrength))); + } + + public void onCellInfoChanged(List<CellInfo> cellInfo) { + CellInfoListener listener = (CellInfoListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onCellInfoChanged(cellInfo))); + } + + public void onPreciseCallStateChanged(PreciseCallState callState) { + PreciseCallStateListener listener = + (PreciseCallStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onPreciseCallStateChanged(callState))); + } + + public void onCallDisconnectCauseChanged(int disconnectCause, int preciseDisconnectCause) { + CallDisconnectCauseListener listener = + (CallDisconnectCauseListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onCallDisconnectCauseChanged( + disconnectCause, preciseDisconnectCause))); + } + + public void onPreciseDataConnectionStateChanged( + PreciseDataConnectionState dataConnectionState) { + PreciseDataConnectionStateListener listener = + (PreciseDataConnectionStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onPreciseDataConnectionStateChanged( + dataConnectionState))); + } + + public void onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo dcRtInfo) { + // default implementation empty + } + + public void onSrvccStateChanged(int state) { + SrvccStateListener listener = (SrvccStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onSrvccStateChanged(state))); + } + + public void onVoiceActivationStateChanged(int activationState) { + VoiceActivationStateListener listener = + (VoiceActivationStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onVoiceActivationStateChanged(activationState))); + } + + public void onDataActivationStateChanged(int activationState) { + DataActivationStateListener listener = + (DataActivationStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onDataActivationStateChanged(activationState))); + } + + public void onUserMobileDataStateChanged(boolean enabled) { + UserMobileDataStateListener listener = + (UserMobileDataStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onUserMobileDataStateChanged(enabled))); + } + + public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { + DisplayInfoListener listener = (DisplayInfoListener)mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onDisplayInfoChanged(telephonyDisplayInfo))); + } + + public void onOemHookRawEvent(byte[] rawData) { + // default implementation empty + } + + public void onCarrierNetworkChange(boolean active) { + CarrierNetworkListener listener = + (CarrierNetworkListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onCarrierNetworkChange(active))); + } + + public void onEmergencyNumberListChanged(Map emergencyNumberList) { + EmergencyNumberListListener listener = + (EmergencyNumberListListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onEmergencyNumberListChanged(emergencyNumberList))); + } + + public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber, + int subscriptionId) { + OutgoingEmergencyCallListener listener = + (OutgoingEmergencyCallListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onOutgoingEmergencyCall(placedEmergencyNumber, + subscriptionId))); + } + + public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber, + int subscriptionId) { + OutgoingEmergencySmsListener listener = + (OutgoingEmergencySmsListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onOutgoingEmergencySms(sentEmergencyNumber, + subscriptionId))); + } + + public void onPhoneCapabilityChanged(PhoneCapability capability) { + PhoneCapabilityListener listener = + (PhoneCapabilityListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onPhoneCapabilityChanged(capability))); + } + + public void onRadioPowerStateChanged(@Annotation.RadioPowerState int state) { + RadioPowerStateListener listener = + (RadioPowerStateListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state))); + } + + public void onCallAttributesChanged(CallAttributes callAttributes) { + CallAttributesListener listener = + (CallAttributesListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onCallAttributesChanged( + callAttributes))); + } + + public void onActiveDataSubIdChanged(int subId) { + ActiveDataSubscriptionIdListener listener = + (ActiveDataSubscriptionIdListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onActiveDataSubscriptionIdChanged( + subId))); + } + + public void onImsCallDisconnectCauseChanged(ImsReasonInfo disconnectCause) { + ImsCallDisconnectCauseListener listener = + (ImsCallDisconnectCauseListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onImsCallDisconnectCauseChanged(disconnectCause))); + } + + public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, + @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) { + RegistrationFailedListener listener = + (RegistrationFailedListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onRegistrationFailed( + cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode))); + // default implementation empty + } + + public void onBarringInfoChanged(BarringInfo barringInfo) { + BarringInfoListener listener = (BarringInfoListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onBarringInfoChanged(barringInfo))); + } + + public void onPhysicalChannelConfigChanged(List<PhysicalChannelConfig> configs) { + PhysicalChannelConfigListener listener = + (PhysicalChannelConfigListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onPhysicalChannelConfigChanged( + configs))); + } + + public void onDataEnabledChanged(boolean enabled, + @TelephonyManager.DataEnabledReason int reason) { + DataEnabledListener listener = + (DataEnabledListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onDataEnabledChanged( + enabled, reason))); + } + + public void onAllowedNetworkTypesChanged(Map allowedNetworkTypesList) { + AllowedNetworkTypesListener listener = + (AllowedNetworkTypesListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onAllowedNetworkTypesChanged(allowedNetworkTypesList))); + } + } +} diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 3c355d4b6f45..459c6e94e4ac 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -209,7 +209,7 @@ public class TelephonyRegistryManager { } /** - * To check the SDK version for {@link #listenWithEventList}. + * To check the SDK version for {@link #listenFromListener}. */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P) @@ -221,24 +221,49 @@ public class TelephonyRegistryManager { * @param pkg Package name * @param featureId Feature ID * @param listener Listener providing callback - * @param events List events + * @param events Events * @param notifyNow Whether to notify instantly */ - public void listenWithEventList(int subId, @NonNull String pkg, @NonNull String featureId, - @NonNull PhoneStateListener listener, @NonNull int[] events, boolean notifyNow) { + public void listenFromListener(int subId, @NonNull String pkg, @NonNull String featureId, + @NonNull PhoneStateListener listener, @NonNull int events, boolean notifyNow) { + if (listener == null) { + throw new IllegalStateException("telephony service is null."); + } + try { + int[] eventsList = getEventsFromBitmask(events).stream().mapToInt(i -> i).toArray(); // subId from PhoneStateListener is deprecated Q on forward, use the subId from // TelephonyManager instance. Keep using subId from PhoneStateListener for pre-Q. if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) { // Since mSubId in PhoneStateListener is deprecated from Q on forward, this is // the only place to set mSubId and its for "informational" only. - listener.mSubId = (events.length == 0) + listener.mSubId = (eventsList.length == 0) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : subId; } else if (listener.mSubId != null) { subId = listener.mSubId; } sRegistry.listenWithEventList( - subId, pkg, featureId, listener.callback, events, notifyNow); + subId, pkg, featureId, listener.callback, eventsList, notifyNow); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Listen for incoming subscriptions + * @param subId Subscription ID + * @param pkg Package name + * @param featureId Feature ID + * @param telephonyCallback Listener providing callback + * @param events List events + * @param notifyNow Whether to notify instantly + */ + private void listenFromCallback(int subId, @NonNull String pkg, @NonNull String featureId, + @NonNull TelephonyCallback telephonyCallback, @NonNull int[] events, + boolean notifyNow) { + try { + sRegistry.listenWithEventList( + subId, pkg, featureId, telephonyCallback.callback, events, notifyNow); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -815,136 +840,137 @@ public class TelephonyRegistryManager { } } - public @NonNull Set<Integer> getEventsFromListener(@NonNull PhoneStateListener listener) { + public @NonNull Set<Integer> getEventsFromCallback( + @NonNull TelephonyCallback telephonyCallback) { Set<Integer> eventList = new ArraySet<>(); - if (listener instanceof PhoneStateListener.ServiceStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.ServiceStateListener) { + eventList.add(TelephonyCallback.EVENT_SERVICE_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.MessageWaitingIndicatorChangedListener) { - eventList.add(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.MessageWaitingIndicatorListener) { + eventList.add(TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED); } - if (listener instanceof PhoneStateListener.CallForwardingIndicatorChangedListener) { - eventList.add(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.CallForwardingIndicatorListener) { + eventList.add(TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED); } - if (listener instanceof PhoneStateListener.CellLocationChangedListener) { - eventList.add(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.CellLocationListener) { + eventList.add(TelephonyCallback.EVENT_CELL_LOCATION_CHANGED); } - if (listener instanceof PhoneStateListener.CallStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_CALL_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.CallStateListener) { + eventList.add(TelephonyCallback.EVENT_CALL_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.DataConnectionStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.DataConnectionStateListener) { + eventList.add(TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.DataActivityListener) { - eventList.add(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.DataActivityListener) { + eventList.add(TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED); } - if (listener instanceof PhoneStateListener.SignalStrengthsChangedListener) { - eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.SignalStrengthsListener) { + eventList.add(TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED); } - if (listener instanceof PhoneStateListener.AlwaysReportedSignalStrengthChangedListener) { - eventList.add(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.AlwaysReportedSignalStrengthListener) { + eventList.add(TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED); } - if (listener instanceof PhoneStateListener.CellInfoChangedListener) { - eventList.add(PhoneStateListener.EVENT_CELL_INFO_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.CellInfoListener) { + eventList.add(TelephonyCallback.EVENT_CELL_INFO_CHANGED); } - if (listener instanceof PhoneStateListener.PreciseCallStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.PreciseCallStateListener) { + eventList.add(TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.CallDisconnectCauseChangedListener) { - eventList.add(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.CallDisconnectCauseListener) { + eventList.add(TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED); } - if (listener instanceof PhoneStateListener.ImsCallDisconnectCauseChangedListener) { - eventList.add(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.ImsCallDisconnectCauseListener) { + eventList.add(TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED); } - if (listener instanceof PhoneStateListener.PreciseDataConnectionStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.PreciseDataConnectionStateListener) { + eventList.add(TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.SrvccStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.SrvccStateListener) { + eventList.add(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.VoiceActivationStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.VoiceActivationStateListener) { + eventList.add(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.DataActivationStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.DataActivationStateListener) { + eventList.add(TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.UserMobileDataStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.UserMobileDataStateListener) { + eventList.add(TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.DisplayInfoChangedListener) { - eventList.add(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.DisplayInfoListener) { + eventList.add(TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED); } - if (listener instanceof PhoneStateListener.EmergencyNumberListChangedListener) { - eventList.add(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.EmergencyNumberListListener) { + eventList.add(TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED); } - if (listener instanceof PhoneStateListener.OutgoingEmergencyCallListener) { - eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL); + if (telephonyCallback instanceof TelephonyCallback.OutgoingEmergencyCallListener) { + eventList.add(TelephonyCallback.EVENT_OUTGOING_EMERGENCY_CALL); } - if (listener instanceof PhoneStateListener.OutgoingEmergencySmsListener) { - eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS); + if (telephonyCallback instanceof TelephonyCallback.OutgoingEmergencySmsListener) { + eventList.add(TelephonyCallback.EVENT_OUTGOING_EMERGENCY_SMS); } - if (listener instanceof PhoneStateListener.PhoneCapabilityChangedListener) { - eventList.add(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.PhoneCapabilityListener) { + eventList.add(TelephonyCallback.EVENT_PHONE_CAPABILITY_CHANGED); } - if (listener instanceof PhoneStateListener.ActiveDataSubscriptionIdChangedListener) { - eventList.add(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.ActiveDataSubscriptionIdListener) { + eventList.add(TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED); } - if (listener instanceof PhoneStateListener.RadioPowerStateChangedListener) { - eventList.add(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.RadioPowerStateListener) { + eventList.add(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED); } - if (listener instanceof PhoneStateListener.CarrierNetworkChangeListener) { - eventList.add(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.CarrierNetworkListener) { + eventList.add(TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED); } - if (listener instanceof PhoneStateListener.RegistrationFailedListener) { - eventList.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE); + if (telephonyCallback instanceof TelephonyCallback.RegistrationFailedListener) { + eventList.add(TelephonyCallback.EVENT_REGISTRATION_FAILURE); } - if (listener instanceof PhoneStateListener.CallAttributesChangedListener) { - eventList.add(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.CallAttributesListener) { + eventList.add(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED); } - if (listener instanceof PhoneStateListener.BarringInfoChangedListener) { - eventList.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.BarringInfoListener) { + eventList.add(TelephonyCallback.EVENT_BARRING_INFO_CHANGED); } - if (listener instanceof PhoneStateListener.PhysicalChannelConfigChangedListener) { - eventList.add(PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.PhysicalChannelConfigListener) { + eventList.add(TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); } - if (listener instanceof PhoneStateListener.DataEnabledChangedListener) { - eventList.add(PhoneStateListener.EVENT_DATA_ENABLED_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.DataEnabledListener) { + eventList.add(TelephonyCallback.EVENT_DATA_ENABLED_CHANGED); } - if (listener instanceof PhoneStateListener.AllowedNetworkTypesChangedListener) { - eventList.add(PhoneStateListener.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED); + if (telephonyCallback instanceof TelephonyCallback.AllowedNetworkTypesListener) { + eventList.add(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED); } return eventList; @@ -955,200 +981,183 @@ public class TelephonyRegistryManager { Set<Integer> eventList = new ArraySet<>(); if ((eventMask & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_SERVICE_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) { - eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED); + eventList.add(TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) { - eventList.add(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED); + eventList.add(TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) { - eventList.add(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED); + eventList.add(TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { - eventList.add(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED); + eventList.add(TelephonyCallback.EVENT_CELL_LOCATION_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_CALL_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_CALL_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_CALL_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) { - eventList.add(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED); + eventList.add(TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) { - eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED); + eventList.add(TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) { - eventList.add(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED); + eventList.add(TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_CELL_INFO) != 0) { - eventList.add(PhoneStateListener.EVENT_CELL_INFO_CHANGED); + eventList.add(TelephonyCallback.EVENT_CELL_INFO_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO) != 0) { - eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED); + eventList.add(TelephonyCallback.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) { - eventList.add(PhoneStateListener.EVENT_OEM_HOOK_RAW); + eventList.add(TelephonyCallback.EVENT_OEM_HOOK_RAW); } if ((eventMask & PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) != 0) { - eventList.add(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) { - eventList.add(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED); + eventList.add(TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) { - eventList.add(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) { - eventList.add(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED); + eventList.add(TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE) != 0) { - eventList.add(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED); + eventList.add(TelephonyCallback.EVENT_PHONE_CAPABILITY_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) { - eventList.add(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED); + eventList.add(TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) { - eventList.add(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED); + eventList.add(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) { - eventList.add(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED); + eventList.add(TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) { - eventList.add(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED); + eventList.add(TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED) != 0) { - eventList.add(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED); + eventList.add(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) { - eventList.add(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED); + eventList.add(TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED); } if ((eventMask & PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL) != 0) { - eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL); + eventList.add(TelephonyCallback.EVENT_OUTGOING_EMERGENCY_CALL); } if ((eventMask & PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS) != 0) { - eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS); + eventList.add(TelephonyCallback.EVENT_OUTGOING_EMERGENCY_SMS); } if ((eventMask & PhoneStateListener.LISTEN_REGISTRATION_FAILURE) != 0) { - eventList.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE); + eventList.add(TelephonyCallback.EVENT_REGISTRATION_FAILURE); } if ((eventMask & PhoneStateListener.LISTEN_BARRING_INFO) != 0) { - eventList.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED); + eventList.add(TelephonyCallback.EVENT_BARRING_INFO_CHANGED); } return eventList; } /** - * Registers a listener object to receive notification of changes - * in specified telephony states. + * Registers a callback object to receive notification of changes in specified telephony states. * <p> - * To register a listener, pass a {@link PhoneStateListener} which implements + * To register a callback, pass a {@link TelephonyCallback} which implements * interfaces of events. For example, - * FakeServiceStateChangedListener extends {@link PhoneStateListener} implements - * {@link PhoneStateListener.ServiceStateChangedListener}. + * FakeServiceStateCallback extends {@link TelephonyCallback} implements + * {@link TelephonyCallback.ServiceStateListener}. * * At registration, and when a specified telephony state changes, the telephony manager invokes - * the appropriate callback method on the listener object and passes the current (updated) + * the appropriate callback method on the callback object and passes the current (updated) * values. * <p> * * If this TelephonyManager object has been created with * {@link TelephonyManager#createForSubscriptionId}, applies to the given subId. * Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}. - * To listen events for multiple subIds, pass a separate listener object to + * To register events for multiple subIds, pass a separate callback object to * each TelephonyManager object created with {@link TelephonyManager#createForSubscriptionId}. * * Note: if you call this method while in the middle of a binder transaction, you <b>must</b> * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A * {@link SecurityException} will be thrown otherwise. * - * This API should be used sparingly -- large numbers of listeners will cause system - * instability. If a process has registered too many listeners without unregistering them, it - * may encounter an {@link IllegalStateException} when trying to register more listeners. + * This API should be used sparingly -- large numbers of callbacks will cause system + * instability. If a process has registered too many callbacks without unregistering them, it + * may encounter an {@link IllegalStateException} when trying to register more callbacks. * - * @param listener The {@link PhoneStateListener} object to register. + * @param callback The {@link TelephonyCallback} object to register. */ - public void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor, int subId, - String pkgName, String attributionTag, @NonNull PhoneStateListener listener, + public void registerTelephonyCallback(@NonNull @CallbackExecutor Executor executor, + int subId, String pkgName, String attributionTag, @NonNull TelephonyCallback callback, boolean notifyNow) { - listener.setExecutor(executor); - registerPhoneStateListener(subId, pkgName, attributionTag, listener, - getEventsFromListener(listener), notifyNow); - } - - public void registerPhoneStateListenerWithEvents(int subId, String pkgName, - String attributionTag, @NonNull PhoneStateListener listener, int events, - boolean notifyNow) { - registerPhoneStateListener( - subId, pkgName, attributionTag, listener, getEventsFromBitmask(events), notifyNow); - } - - private void registerPhoneStateListener(int subId, - String pkgName, String attributionTag, @NonNull PhoneStateListener listener, - @NonNull Set<Integer> events, boolean notifyNow) { - if (listener == null) { + if (callback == null) { throw new IllegalStateException("telephony service is null."); } - - listenWithEventList(subId, pkgName, attributionTag, listener, - events.stream().mapToInt(i -> i).toArray(), notifyNow); + callback.init(executor); + listenFromCallback(subId, pkgName, attributionTag, callback, + getEventsFromCallback(callback).stream().mapToInt(i -> i).toArray(), notifyNow); } /** - * Unregister an existing {@link PhoneStateListener}. + * Unregister an existing {@link TelephonyCallback}. * - * @param listener The {@link PhoneStateListener} object to unregister. + * @param callback The {@link TelephonyCallback} object to unregister. */ - public void unregisterPhoneStateListener(int subId, String pkgName, String attributionTag, - @NonNull PhoneStateListener listener, - boolean notifyNow) { - listenWithEventList(subId, pkgName, attributionTag, listener, new int[0], notifyNow); + public void unregisterTelephonyCallback(int subId, String pkgName, String attributionTag, + @NonNull TelephonyCallback callback, boolean notifyNow) { + listenFromCallback(subId, pkgName, attributionTag, callback, new int[0], notifyNow); } } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 2b577d04b18d..3a3302475069 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -68,7 +68,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true"); DEFAULT_FLAGS.put("settings_tether_all_in_one", "false"); - DEFAULT_FLAGS.put("settings_silky_home", "false"); + DEFAULT_FLAGS.put("settings_silky_home", "true"); DEFAULT_FLAGS.put("settings_contextual_home", "false"); DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false"); DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "false"); diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS index 14aa38682d2b..5425c214de1f 100644 --- a/core/java/android/util/OWNERS +++ b/core/java/android/util/OWNERS @@ -2,5 +2,7 @@ per-file FeatureFlagUtils.java = sbasi@google.com per-file FeatureFlagUtils.java = tmfang@google.com per-file FeatureFlagUtils.java = asapperstein@google.com -per-file TypedValue.java = file:/core/java/android/content/res/OWNERS per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS +per-file TypedValue.java = file:/core/java/android/content/res/OWNERS + +per-file PackageUtils.java = file:/core/java/android/content/pm/OWNERS diff --git a/core/java/android/util/SizeF.java b/core/java/android/util/SizeF.java index 2edc4a7ff588..c77a02434941 100644 --- a/core/java/android/util/SizeF.java +++ b/core/java/android/util/SizeF.java @@ -16,8 +16,12 @@ package android.util; -import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkArgumentFinite; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; /** * Immutable class for describing width and height dimensions in some arbitrary @@ -26,7 +30,7 @@ import static com.android.internal.util.Preconditions.checkArgumentFinite; * Width and height are finite values stored as a floating point representation. * </p> */ -public final class SizeF { +public final class SizeF implements Parcelable { /** * Create a new immutable SizeF instance. * @@ -161,4 +165,43 @@ public final class SizeF { private final float mWidth; private final float mHeight; + + /** + * Parcelable interface methods + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Write this size to the specified parcel. To restore a size from a parcel, use the + * {@link #CREATOR}. + * @param out The parcel to write the point's coordinates into + */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeFloat(mWidth); + out.writeFloat(mHeight); + } + + public static final @NonNull Creator<SizeF> CREATOR = new Creator<SizeF>() { + /** + * Return a new size from the data in the specified parcel. + */ + @Override + public @NonNull SizeF createFromParcel(@NonNull Parcel in) { + float width = in.readFloat(); + float height = in.readFloat(); + return new SizeF(width, height); + } + + /** + * Return an array of sizes of the specified size. + */ + @Override + public @NonNull SizeF[] newArray(int size) { + return new SizeF[size]; + } + }; } diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index 6718e93f908c..05c8617294da 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -510,10 +510,12 @@ public class SparseArray<E> implements Cloneable { } /** + * Compares the contents of this {@link SparseArray} to the specified {@link SparseArray}. + * * For backwards compatibility reasons, {@link Object#equals(Object)} cannot be implemented, * so this serves as a manually invoked alternative. */ - public boolean contentEquals(@Nullable SparseArray<E> other) { + public boolean contentEquals(@Nullable SparseArray<?> other) { if (other == null) { return false; } @@ -534,6 +536,9 @@ public class SparseArray<E> implements Cloneable { } /** + * Returns a hash code value for the contents of this {@link SparseArray}, combining the + * {@link Objects#hashCode(Object)} result of all its keys and values. + * * For backwards compatibility, {@link Object#hashCode()} cannot be implemented, so this serves * as a manually invoked alternative. */ diff --git a/core/java/android/util/apk/OWNERS b/core/java/android/util/apk/OWNERS new file mode 100644 index 000000000000..52c95501e541 --- /dev/null +++ b/core/java/android/util/apk/OWNERS @@ -0,0 +1 @@ +include /core/java/android/content/pm/OWNERS diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 9473845b15e6..e5a137cd13f3 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -86,6 +86,12 @@ public final class AccessibilityInteractionController { // accessibility from hanging private static final long REQUEST_PREPARER_TIMEOUT_MS = 500; + // Callbacks should have the same configuration of the flags below to allow satisfying a pending + // node request on prefetch + private static final int FLAGS_AFFECTING_REPORTED_DATA = + AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS + | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; + private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); @@ -114,6 +120,9 @@ public final class AccessibilityInteractionController { private AddNodeInfosForViewId mAddNodeInfosForViewId; @GuardedBy("mLock") + private ArrayList<Message> mPendingFindNodeByIdMessages; + + @GuardedBy("mLock") private int mNumActiveRequestPreparers; @GuardedBy("mLock") private List<MessageHolder> mMessagesWaitingForRequestPreparer; @@ -128,6 +137,7 @@ public final class AccessibilityInteractionController { mViewRootImpl = viewRootImpl; mPrefetcher = new AccessibilityNodePrefetcher(); mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class); + mPendingFindNodeByIdMessages = new ArrayList<>(); } private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, @@ -177,7 +187,11 @@ public final class AccessibilityInteractionController { args.arg4 = arguments; message.obj = args; - scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); + synchronized (mLock) { + mPendingFindNodeByIdMessages.add(message); + scheduleMessage(message, interrogatingPid, interrogatingTid, + CONSIDER_REQUEST_PREPARERS); + } } /** @@ -315,6 +329,9 @@ public final class AccessibilityInteractionController { } private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { + synchronized (mLock) { + mPendingFindNodeByIdMessages.remove(message); + } final int flags = message.arg1; SomeArgs args = (SomeArgs) message.obj; @@ -329,22 +346,58 @@ public final class AccessibilityInteractionController { args.recycle(); - List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; - infos.clear(); + View rootView = null; + AccessibilityNodeInfo rootNode = null; try { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; - final View root = findViewByAccessibilityId(accessibilityViewId); - if (root != null && isShown(root)) { - mPrefetcher.prefetchAccessibilityNodeInfos( - root, virtualDescendantId, flags, infos, arguments); + rootView = findViewByAccessibilityId(accessibilityViewId); + if (rootView != null && isShown(rootView)) { + rootNode = populateAccessibilityNodeInfoForView( + rootView, arguments, virtualDescendantId); } } finally { - updateInfosForViewportAndReturnFindNodeResult( - infos, callback, interactionId, spec, interactiveRegion); + updateInfoForViewportAndReturnFindNodeResult( + rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode), + callback, interactionId, spec, interactiveRegion); } + ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; + infos.clear(); + mPrefetcher.prefetchAccessibilityNodeInfos( + rootView, rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode), + virtualDescendantId, flags, infos); + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + updateInfosForViewPort(infos, spec, interactiveRegion); + returnPrefetchResult(interactionId, infos, callback); + returnPendingFindAccessibilityNodeInfosInPrefetch(rootNode, infos, flags); + } + + private AccessibilityNodeInfo populateAccessibilityNodeInfoForView( + View view, Bundle arguments, int virtualViewId) { + AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); + // Determine if we'll be populating extra data + final String extraDataRequested = (arguments == null) ? null + : arguments.getString(EXTRA_DATA_REQUESTED_KEY); + AccessibilityNodeInfo root = null; + if (provider == null) { + root = view.createAccessibilityNodeInfo(); + if (root != null) { + if (extraDataRequested != null) { + view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments); + } + } + } else { + root = provider.createAccessibilityNodeInfo(virtualViewId); + if (root != null) { + if (extraDataRequested != null) { + provider.addExtraDataToAccessibilityNodeInfo( + virtualViewId, root, extraDataRequested, arguments); + } + } + } + return root; } public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, @@ -383,7 +436,8 @@ public final class AccessibilityInteractionController { final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; infos.clear(); try { - if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { + if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null + || viewId == null) { return; } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; @@ -402,6 +456,7 @@ public final class AccessibilityInteractionController { mAddNodeInfosForViewId.reset(); } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -484,6 +539,7 @@ public final class AccessibilityInteractionController { } } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -575,6 +631,7 @@ public final class AccessibilityInteractionController { } } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( focused, callback, interactionId, spec, interactiveRegion); } @@ -629,6 +686,7 @@ public final class AccessibilityInteractionController { } } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( next, callback, interactionId, spec, interactiveRegion); } @@ -785,33 +843,6 @@ public final class AccessibilityInteractionController { } } - private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, - MagnificationSpec spec) { - if (infos == null) { - return; - } - final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; - if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { - final int infoCount = infos.size(); - for (int i = 0; i < infoCount; i++) { - AccessibilityNodeInfo info = infos.get(i); - applyAppScaleAndMagnificationSpecIfNeeded(info, spec); - } - } - } - - private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, - Region interactiveRegion) { - if (interactiveRegion == null || infos == null) { - return; - } - final int infoCount = infos.size(); - for (int i = 0; i < infoCount; i++) { - AccessibilityNodeInfo info = infos.get(i); - adjustIsVisibleToUserIfNeeded(info, interactiveRegion); - } - } - private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion) { if (interactiveRegion == null || info == null) { @@ -832,17 +863,6 @@ public final class AccessibilityInteractionController { return false; } - private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) { - if (infos == null || shouldBypassAdjustBoundsInScreen()) { - return; - } - final int infoCount = infos.size(); - for (int i = 0; i < infoCount; i++) { - final AccessibilityNodeInfo info = infos.get(i); - adjustBoundsInScreenIfNeeded(info); - } - } - private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) { if (info == null || shouldBypassAdjustBoundsInScreen()) { return; @@ -890,17 +910,6 @@ public final class AccessibilityInteractionController { return screenMatrix == null || screenMatrix.isIdentity(); } - private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) { - if (infos == null || shouldBypassAssociateLeashedParent()) { - return; - } - final int infoCount = infos.size(); - for (int i = 0; i < infoCount; i++) { - final AccessibilityNodeInfo info = infos.get(i); - associateLeashedParentIfNeeded(info); - } - } - private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) { if (info == null || shouldBypassAssociateLeashedParent()) { return; @@ -974,18 +983,46 @@ public final class AccessibilityInteractionController { return (appScale != 1.0f || (spec != null && !spec.isNop())); } + private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec, + Region interactiveRegion) { + for (int i = 0; i < infos.size(); i++) { + updateInfoForViewPort(infos.get(i), spec, interactiveRegion); + } + } + + private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec, + Region interactiveRegion) { + associateLeashedParentIfNeeded(info); + applyScreenMatrixIfNeeded(info); + adjustBoundsInScreenIfNeeded(info); + // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, + // then impact the visibility result, we need to adjust visibility before apply scale. + adjustIsVisibleToUserIfNeeded(info, interactiveRegion); + applyAppScaleAndMagnificationSpecIfNeeded(info, spec); + } + private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion) { + if (infos != null) { + updateInfosForViewPort(infos, spec, interactiveRegion); + } + returnFindNodesResult(infos, callback, interactionId); + } + + private void returnFindNodeResult(AccessibilityNodeInfo info, + IAccessibilityInteractionConnectionCallback callback, + int interactionId) { + try { + callback.setFindAccessibilityNodeInfoResult(info, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + + private void returnFindNodesResult(List<AccessibilityNodeInfo> infos, + IAccessibilityInteractionConnectionCallback callback, int interactionId) { try { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; - associateLeashedParentIfNeeded(infos); - applyScreenMatrixIfNeeded(infos); - adjustBoundsInScreenIfNeeded(infos); - // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, - // then impact the visibility result, we need to adjust visibility before apply scale. - adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); - applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); callback.setFindAccessibilityNodeInfosResult(infos, interactionId); if (infos != null) { infos.clear(); @@ -995,22 +1032,80 @@ public final class AccessibilityInteractionController { } } + private void returnPendingFindAccessibilityNodeInfosInPrefetch(AccessibilityNodeInfo rootNode, + List<AccessibilityNodeInfo> infos, int flags) { + + AccessibilityNodeInfo satisfiedPendingRequestPrefetchedNode = null; + IAccessibilityInteractionConnectionCallback satisfiedPendingRequestCallback = null; + int satisfiedPendingRequestInteractionId = AccessibilityInteractionClient.NO_ID; + + synchronized (mLock) { + for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) { + final Message pendingMessage = mPendingFindNodeByIdMessages.get(i); + final int pendingFlags = pendingMessage.arg1; + if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA) + != (flags & FLAGS_AFFECTING_REPORTED_DATA)) { + continue; + } + SomeArgs args = (SomeArgs) pendingMessage.obj; + final int accessibilityViewId = args.argi1; + final int virtualDescendantId = args.argi2; + + satisfiedPendingRequestPrefetchedNode = nodeWithIdFromList(rootNode, + infos, AccessibilityNodeInfo.makeNodeId( + accessibilityViewId, virtualDescendantId)); + + if (satisfiedPendingRequestPrefetchedNode != null) { + satisfiedPendingRequestCallback = + (IAccessibilityInteractionConnectionCallback) args.arg1; + satisfiedPendingRequestInteractionId = args.argi3; + mHandler.removeMessages( + PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID, + pendingMessage.obj); + args.recycle(); + break; + } + } + mPendingFindNodeByIdMessages.clear(); + } + + if (satisfiedPendingRequestPrefetchedNode != null) { + returnFindNodeResult( + AccessibilityNodeInfo.obtain(satisfiedPendingRequestPrefetchedNode), + satisfiedPendingRequestCallback, satisfiedPendingRequestInteractionId); + } + } + + private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo rootNode, + List<AccessibilityNodeInfo> infos, long nodeId) { + if (rootNode != null && rootNode.getSourceNodeId() == nodeId) { + return rootNode; + } + for (int j = 0; j < infos.size(); j++) { + AccessibilityNodeInfo info = infos.get(j); + if (info.getSourceNodeId() == nodeId) { + return info; + } + } + return null; + } + + private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos, + IAccessibilityInteractionConnectionCallback callback) { + if (infos.size() > 0) { + try { + callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId); + } catch (RemoteException re) { + /* ignore - other side isn't too bothered if this doesn't arrive */ + } + } + } + private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion) { - try { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; - associateLeashedParentIfNeeded(info); - applyScreenMatrixIfNeeded(info); - adjustBoundsInScreenIfNeeded(info); - // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, - // then impact the visibility result, we need to adjust visibility before apply scale. - adjustIsVisibleToUserIfNeeded(info, interactiveRegion); - applyAppScaleAndMagnificationSpecIfNeeded(info, spec); - callback.setFindAccessibilityNodeInfoResult(info, interactionId); - } catch (RemoteException re) { - /* ignore - the other side will time out */ - } + updateInfoForViewPort(info, spec, interactiveRegion); + returnFindNodeResult(info, callback, interactionId); } private boolean handleClickableSpanActionUiThread( @@ -1053,56 +1148,45 @@ public final class AccessibilityInteractionController { private final ArrayList<View> mTempViewList = new ArrayList<View>(); - public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, - List<AccessibilityNodeInfo> outInfos, Bundle arguments) { + public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, + int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) { + if (root == null) { + return; + } AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); - // Determine if we'll be populating extra data - final String extraDataRequested = (arguments == null) ? null - : arguments.getString(EXTRA_DATA_REQUESTED_KEY); if (provider == null) { - AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); - if (root != null) { - if (extraDataRequested != null) { - view.addExtraDataToAccessibilityNodeInfo( - root, extraDataRequested, arguments); - } - outInfos.add(root); - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { - prefetchPredecessorsOfRealNode(view, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfRealNode(view, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { - prefetchDescendantsOfRealNode(view, outInfos); - } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + prefetchPredecessorsOfRealNode(view, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { + prefetchSiblingsOfRealNode(view, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + prefetchDescendantsOfRealNode(view, outInfos); } } else { - final AccessibilityNodeInfo root = - provider.createAccessibilityNodeInfo(virtualViewId); - if (root != null) { - if (extraDataRequested != null) { - provider.addExtraDataToAccessibilityNodeInfo( - virtualViewId, root, extraDataRequested, arguments); - } - outInfos.add(root); - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { - prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { - prefetchDescendantsOfVirtualNode(root, provider, outInfos); - } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { + prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + prefetchDescendantsOfVirtualNode(root, provider, outInfos); } } if (ENFORCE_NODE_TREE_CONSISTENT) { - enforceNodeTreeConsistent(outInfos); + enforceNodeTreeConsistent(root, outInfos); } } - private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { + private boolean shouldStopPrefetching(List prefetchededInfos) { + return mHandler.hasUserInteractiveMessagesWaiting() + || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE; + } + + private void enforceNodeTreeConsistent( + AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) { LongSparseArray<AccessibilityNodeInfo> nodeMap = new LongSparseArray<AccessibilityNodeInfo>(); final int nodeCount = nodes.size(); @@ -1113,7 +1197,6 @@ public final class AccessibilityInteractionController { // If the nodes are a tree it does not matter from // which node we start to search for the root. - AccessibilityNodeInfo root = nodeMap.valueAt(0); AccessibilityNodeInfo parent = root; while (parent != null) { root = parent; @@ -1180,9 +1263,11 @@ public final class AccessibilityInteractionController { private void prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos) { + if (shouldStopPrefetching(outInfos)) { + return; + } ViewParent parent = view.getParentForAccessibility(); - while (parent instanceof View - && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + while (parent instanceof View && !shouldStopPrefetching(outInfos)) { View parentView = (View) parent; AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); if (info != null) { @@ -1194,6 +1279,9 @@ public final class AccessibilityInteractionController { private void prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos) { + if (shouldStopPrefetching(outInfos)) { + return; + } ViewParent parent = current.getParentForAccessibility(); if (parent instanceof ViewGroup) { ViewGroup parentGroup = (ViewGroup) parent; @@ -1203,7 +1291,7 @@ public final class AccessibilityInteractionController { parentGroup.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } View child = children.get(i); @@ -1231,7 +1319,7 @@ public final class AccessibilityInteractionController { private void prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos) { - if (!(root instanceof ViewGroup)) { + if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) { return; } HashMap<View, AccessibilityNodeInfo> addedChildren = @@ -1242,7 +1330,7 @@ public final class AccessibilityInteractionController { root.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } View child = children.get(i); @@ -1267,7 +1355,7 @@ public final class AccessibilityInteractionController { } finally { children.clear(); } - if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (!shouldStopPrefetching(outInfos)) { for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { View addedChild = entry.getKey(); AccessibilityNodeInfo virtualRoot = entry.getValue(); @@ -1289,7 +1377,7 @@ public final class AccessibilityInteractionController { long parentNodeId = root.getParentNodeId(); int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } final int virtualDescendantId = @@ -1334,7 +1422,7 @@ public final class AccessibilityInteractionController { if (parent != null) { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } final long childNodeId = parent.getChildId(i); @@ -1359,7 +1447,7 @@ public final class AccessibilityInteractionController { final int initialOutInfosSize = outInfos.size(); final int childCount = root.getChildCount(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } final long childNodeId = root.getChildId(i); @@ -1369,7 +1457,7 @@ public final class AccessibilityInteractionController { outInfos.add(child); } } - if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (!shouldStopPrefetching(outInfos)) { final int addedChildCount = outInfos.size() - initialOutInfosSize; for (int i = 0; i < addedChildCount; i++) { AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); @@ -1478,6 +1566,10 @@ public final class AccessibilityInteractionController { boolean hasAccessibilityCallback(Message message) { return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false; } + + boolean hasUserInteractiveMessagesWaiting() { + return hasMessagesOrCallbacks(); + } } private final class AddNodeInfosForViewId implements Predicate<View> { diff --git a/core/java/android/view/AppTransitionAnimationSpec.java b/core/java/android/view/AppTransitionAnimationSpec.java index 877bb5684910..3215f2bc2a50 100644 --- a/core/java/android/view/AppTransitionAnimationSpec.java +++ b/core/java/android/view/AppTransitionAnimationSpec.java @@ -28,8 +28,8 @@ public class AppTransitionAnimationSpec implements Parcelable { public AppTransitionAnimationSpec(Parcel in) { taskId = in.readInt(); - rect = in.readParcelable(null); - buffer = in.readParcelable(null); + rect = in.readTypedObject(Rect.CREATOR); + buffer = in.readTypedObject(HardwareBuffer.CREATOR); } @Override @@ -40,8 +40,8 @@ public class AppTransitionAnimationSpec implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(taskId); - dest.writeParcelable(rect, 0 /* flags */); - dest.writeParcelable(buffer, 0); + dest.writeTypedObject(rect, 0 /* flags */); + dest.writeTypedObject(buffer, 0 /* flags */); } public static final @android.annotation.NonNull Parcelable.Creator<AppTransitionAnimationSpec> CREATOR diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 59299f6b15eb..5c65c659e576 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -744,7 +744,10 @@ public final class Choreographer { } try { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "Choreographer#doFrame " + vsyncEventData.id); + } AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); diff --git a/core/java/android/view/CrossWindowBlurListeners.java b/core/java/android/view/CrossWindowBlurListeners.java new file mode 100644 index 000000000000..5a1b850133cb --- /dev/null +++ b/core/java/android/view/CrossWindowBlurListeners.java @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.ArraySet; +import android.util.Log; + +import java.util.function.Consumer; + +/** + * Class that holds all registered {@link CrossWindowBlurEnabledListener}s. It listens + * for updates from the WindowManagerService and updates all registered listeners. + * @hide + */ +public final class CrossWindowBlurListeners { + private static final String TAG = "CrossWindowBlurListeners"; + + // property for background blur support in surface flinger + private static final String BLUR_PROPERTY = "ro.surface_flinger.supports_background_blur"; + public static final boolean CROSS_WINDOW_BLUR_SUPPORTED = + SystemProperties.get(BLUR_PROPERTY, "default").equals("1"); + + private static volatile CrossWindowBlurListeners sInstance; + private static final Object sLock = new Object(); + + private final BlurEnabledListenerInternal mListenerInternal = new BlurEnabledListenerInternal(); + private final ArraySet<Consumer<Boolean>> mListeners = new ArraySet(); + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + private boolean mInternalListenerAttached = false; + private boolean mCrossWindowBlurEnabled; + + private CrossWindowBlurListeners() {} + + /** + * Returns a CrossWindowBlurListeners instance + */ + public static CrossWindowBlurListeners getInstance() { + CrossWindowBlurListeners instance = sInstance; + if (instance == null) { + + synchronized (sLock) { + instance = sInstance; + if (instance == null) { + instance = new CrossWindowBlurListeners(); + sInstance = instance; + } + } + } + return instance; + } + + boolean isCrossWindowBlurEnabled() { + synchronized (sLock) { + attachInternalListenerIfNeededLocked(); + return mCrossWindowBlurEnabled; + } + } + + void addListener(Consumer<Boolean> listener) { + if (listener == null) return; + + synchronized (sLock) { + attachInternalListenerIfNeededLocked(); + + mListeners.add(listener); + notifyListenerOnMain(listener, mCrossWindowBlurEnabled); + } + } + + + void removeListener(Consumer<Boolean> listener) { + if (listener == null) return; + + synchronized (sLock) { + mListeners.remove(listener); + + if (mInternalListenerAttached && mListeners.size() == 0) { + try { + WindowManagerGlobal.getWindowManagerService() + .unregisterCrossWindowBlurEnabledListener(mListenerInternal); + mInternalListenerAttached = false; + } catch (RemoteException e) { + Log.d(TAG, "Could not unregister ICrossWindowBlurEnabledListener"); + } + } + } + } + + private void attachInternalListenerIfNeededLocked() { + if (!mInternalListenerAttached) { + try { + mCrossWindowBlurEnabled = WindowManagerGlobal.getWindowManagerService() + .registerCrossWindowBlurEnabledListener(mListenerInternal); + mInternalListenerAttached = true; + } catch (RemoteException e) { + Log.d(TAG, "Could not register ICrossWindowBlurEnabledListener"); + } + } + } + + private void notifyListenerOnMain(Consumer<Boolean> listener, boolean enabled) { + mMainHandler.post(() -> { + listener.accept(enabled); + }); + } + + private final class BlurEnabledListenerInternal extends ICrossWindowBlurEnabledListener.Stub { + @Override + public void onCrossWindowBlurEnabledChanged(boolean enabled) { + synchronized (sLock) { + mCrossWindowBlurEnabled = enabled; + + for (int i = 0; i < mListeners.size(); i++) { + notifyListenerOnMain(mListeners.valueAt(i), enabled); + } + } + } + } +} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0ba1dfee16f3..8117c963b959 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -34,6 +34,7 @@ import android.graphics.ColorSpace; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Build; @@ -1181,6 +1182,18 @@ public final class Display { } /** + * Returns the product-specific information about the display or the directly connected + * device on the display chain. + * For example, if the display is transitively connected, this field may contain product + * information about the intermediate device. + * Returns {@code null} if product information is not available. + */ + @Nullable + public DeviceProductInfo getDeviceProductInfo() { + return mDisplayInfo.deviceProductInfo; + } + + /** * Gets display metrics that describe the size and density of this display. * The size returned by this method does not necessarily represent the * actual raw size (native resolution) of the display. diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 2a00b5a2e513..655f42308a1f 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -810,6 +810,9 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_TRUSTED) != 0) { result.append(", FLAG_TRUSTED"); } + if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) { + result.append(", FLAG_OWN_DISPLAY_GROUP"); + } return result.toString(); } } diff --git a/core/java/android/view/ICrossWindowBlurEnabledListener.aidl b/core/java/android/view/ICrossWindowBlurEnabledListener.aidl new file mode 100644 index 000000000000..69286e20fe17 --- /dev/null +++ b/core/java/android/view/ICrossWindowBlurEnabledListener.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +/** + * Listener to be invoked when cross-window blur is enabled or disabled. + * {@hide} + */ +oneway interface ICrossWindowBlurEnabledListener { + /** + * Method that will be invoked when cross-window blur is enabled or disabled. + * @param enabled True if cross-window blur is enabled. + */ + void onCrossWindowBlurEnabledChanged(boolean enabled); +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index afdf798d03ce..54778007c6ff 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -35,6 +35,7 @@ import android.os.ParcelFileDescriptor; import android.view.DisplayCutout; import android.view.IApplicationToken; import android.view.IAppTransitionAnimationSpecsFuture; +import android.view.ICrossWindowBlurEnabledListener; import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowListener; import android.view.IDisplayFoldListener; @@ -730,7 +731,7 @@ interface IWindowManager void showGlobalActions(); /** - * Sets layer tracing flags for SurfaceFlingerTrace. + * Sets layer tracing flags for SurfaceFlingerTrace. * * @param flags see definition in SurfaceTracing.cpp */ @@ -799,4 +800,20 @@ interface IWindowManager * @param clientToken the window context's token */ void unregisterWindowContextListener(IBinder clientToken); + + /** + * Registers a listener, which is to be called whenever cross-window blur is enabled/disabled. + * + * @param listener the listener to be registered + * @return true if cross-window blur is currently enabled; false otherwise + */ + boolean registerCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener); + + /** + * Unregisters a listener which was registered with + * {@link #registerCrossWindowBlurEnabledListener()}. + * + * @param listener the listener to be unregistered + */ + void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener); } diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 4a5c95f43d46..d23a1e5992cc 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -116,8 +116,9 @@ public final class ImeFocusController { if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) { return; } + View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; if (DEBUG) { - Log.v(TAG, "onWindowFocus: " + focusedView + Log.v(TAG, "onWindowFocus: " + viewForWindowFocus + " softInputMode=" + InputMethodDebug.softInputModeToString( windowAttribute.softInputMode)); } @@ -128,8 +129,8 @@ public final class ImeFocusController { if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true"); forceFocus = true; } + // Update mNextServedView when focusedView changed. - final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; onViewFocusChanged(viewForWindowFocus, true); // Starting new input when the next focused view is same as served view but the currently diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java new file mode 100644 index 000000000000..c159a127f4eb --- /dev/null +++ b/core/java/android/view/InputEventAssigner.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 android.os.IInputConstants.INVALID_INPUT_EVENT_ID; +import static android.view.InputDevice.SOURCE_TOUCHSCREEN; + +/** + * Process input events and assign input event id to a specific frame. + * + * The assigned input event id is determined by where the current gesture is relative to the vsync. + * In the middle of the gesture (we already processed some input events, and already received at + * least 1 vsync), the latest InputEvent is assigned to the next frame. + * If a gesture just started, then the ACTION_DOWN event will be assigned to the next frame. + * + * Consider the following sequence: + * DOWN -> VSYNC 1 -> MOVE 1 -> MOVE 2 -> VSYNC 2. + * + * For VSYNC 1, we will assign the "DOWN" input event. + * For VSYNC 2, we will assign the "MOVE 2" input event. + * + * Consider another sequence: + * DOWN -> MOVE 1 -> MOVE 2 -> VSYNC 1 -> MOVE 3 -> VSYNC 2. + * + * For VSYNC 1, we will still assign the "DOWN" input event. That means that "MOVE 1" and "MOVE 2" + * events are not attributed to any frame. + * For VSYNC 2, the "MOVE 3" input event will be assigned. + * + * @hide + */ +public class InputEventAssigner { + private static final String TAG = "InputEventAssigner"; + private boolean mHasUnprocessedDown = false; + private int mEventId = INVALID_INPUT_EVENT_ID; + + /** + * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset + * the 'down' state to assign the latest input event to the current frame. + */ + public void onChoreographerCallback() { + // Mark completion of this frame. Use newest input event from now on. + mHasUnprocessedDown = false; + } + + /** + * Process the provided input event to determine which event id to assign to the current frame. + * @param event the input event currently being processed + * @return the id of the input event to use for the current frame + */ + public int processEvent(InputEvent event) { + if (event instanceof KeyEvent) { + // We will not do any special handling for key events + return event.getId(); + } + + if (event instanceof MotionEvent) { + MotionEvent motionEvent = (MotionEvent) event; + final int action = motionEvent.getActionMasked(); + + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mHasUnprocessedDown = false; + } + if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) { + mHasUnprocessedDown = true; + mEventId = event.getId(); + // This will remain 'true' even if we receive a MOVE event, as long as choreographer + // hasn't invoked the 'CALLBACK_INPUT' callback. + } + // Don't update the event id if we haven't processed DOWN yet. + if (!mHasUnprocessedDown) { + mEventId = event.getId(); + } + return mEventId; + } + + throw new IllegalArgumentException("Received unexpected " + event); + } +} diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index d67439cc9de2..6801c27851a9 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3589,6 +3589,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { msg.append(", deviceId=").append(getDeviceId()); msg.append(", source=0x").append(Integer.toHexString(getSource())); msg.append(", displayId=").append(getDisplayId()); + msg.append(", eventId=").append(getId()); } msg.append(" }"); return msg.toString(); diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 5ce4c50cc85c..6c8753b95cc1 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -27,7 +27,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; -import android.widget.FrameLayout; +import android.widget.RelativeLayout; import android.widget.RemoteViews; import com.android.internal.R; @@ -42,7 +42,7 @@ import java.util.ArrayList; * @hide */ @RemoteViews.RemoteView -public class NotificationHeaderView extends FrameLayout { +public class NotificationHeaderView extends RelativeLayout { private final int mHeadingEndMargin; private final int mTouchableHeight; private OnClickListener mExpandClickListener; @@ -159,7 +159,7 @@ public class NotificationHeaderView extends FrameLayout { * @param extraMarginEnd extra margin in px */ public void setTopLineExtraMarginEnd(int extraMarginEnd) { - mTopLineView.setHeaderTextMarginEnd(extraMarginEnd + mHeadingEndMargin); + mTopLineView.setHeaderTextMarginEnd(extraMarginEnd); } /** @@ -181,12 +181,15 @@ public class NotificationHeaderView extends FrameLayout { * @return extra margin */ public int getTopLineExtraMarginEnd() { - return mTopLineView.getHeaderTextMarginEnd() - mHeadingEndMargin; + return mTopLineView.getHeaderTextMarginEnd(); } /** * Get the base margin at the end of the top line view. * Add this to {@link #getTopLineExtraMarginEnd()} to get the total margin of the top line. + * <p> + * NOTE: This method's result is only valid if the expander does not have a number. Currently + * only groups headers and conversations have numbers, so this is safe to use by MediaStyle. * * @return base margin */ diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index e66b17aa4426..cbb86de4785f 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -57,6 +57,7 @@ per-file ViewRootImpl.java = file:/graphics/java/android/graphics/OWNERS per-file ViewRootImpl.java = file:/services/core/java/com/android/server/input/OWNERS per-file ViewRootImpl.java = file:/services/core/java/com/android/server/wm/OWNERS per-file ViewRootImpl.java = file:/core/java/android/view/inputmethod/OWNERS +per-file AccessibilityInteractionController.java = file:/services/accessibility/OWNERS # WindowManager per-file DisplayCutout.aidl = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java index a5ff19ee3312..ea9799584e20 100644 --- a/core/java/android/view/RemoteAnimationDefinition.java +++ b/core/java/android/view/RemoteAnimationDefinition.java @@ -184,13 +184,13 @@ public class RemoteAnimationDefinition implements Parcelable { } private RemoteAnimationAdapterEntry(Parcel in) { - adapter = in.readParcelable(RemoteAnimationAdapter.class.getClassLoader()); + adapter = in.readTypedObject(RemoteAnimationAdapter.CREATOR); activityTypeFilter = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(adapter, flags); + dest.writeTypedObject(adapter, flags); dest.writeInt(activityTypeFilter); } diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index 258a72cbcab4..b1b670f5e0c9 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -222,20 +222,20 @@ public class RemoteAnimationTarget implements Parcelable { public RemoteAnimationTarget(Parcel in) { taskId = in.readInt(); mode = in.readInt(); - leash = in.readParcelable(null); + leash = in.readTypedObject(SurfaceControl.CREATOR); isTranslucent = in.readBoolean(); - clipRect = in.readParcelable(null); - contentInsets = in.readParcelable(null); + clipRect = in.readTypedObject(Rect.CREATOR); + contentInsets = in.readTypedObject(Rect.CREATOR); prefixOrderIndex = in.readInt(); - position = in.readParcelable(null); - localBounds = in.readParcelable(null); - sourceContainerBounds = in.readParcelable(null); - screenSpaceBounds = in.readParcelable(null); - windowConfiguration = in.readParcelable(null); + position = in.readTypedObject(Point.CREATOR); + localBounds = in.readTypedObject(Rect.CREATOR); + sourceContainerBounds = in.readTypedObject(Rect.CREATOR); + screenSpaceBounds = in.readTypedObject(Rect.CREATOR); + windowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR); isNotInRecents = in.readBoolean(); - startLeash = in.readParcelable(null); - startBounds = in.readParcelable(null); - pictureInPictureParams = in.readParcelable(null); + startLeash = in.readTypedObject(SurfaceControl.CREATOR); + startBounds = in.readTypedObject(Rect.CREATOR); + pictureInPictureParams = in.readTypedObject(PictureInPictureParams.CREATOR); } @Override @@ -247,20 +247,20 @@ public class RemoteAnimationTarget implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(taskId); dest.writeInt(mode); - dest.writeParcelable(leash, 0 /* flags */); + dest.writeTypedObject(leash, 0 /* flags */); dest.writeBoolean(isTranslucent); - dest.writeParcelable(clipRect, 0 /* flags */); - dest.writeParcelable(contentInsets, 0 /* flags */); + dest.writeTypedObject(clipRect, 0 /* flags */); + dest.writeTypedObject(contentInsets, 0 /* flags */); dest.writeInt(prefixOrderIndex); - dest.writeParcelable(position, 0 /* flags */); - dest.writeParcelable(localBounds, 0 /* flags */); - dest.writeParcelable(sourceContainerBounds, 0 /* flags */); - dest.writeParcelable(screenSpaceBounds, 0 /* flags */); - dest.writeParcelable(windowConfiguration, 0 /* flags */); + dest.writeTypedObject(position, 0 /* flags */); + dest.writeTypedObject(localBounds, 0 /* flags */); + dest.writeTypedObject(sourceContainerBounds, 0 /* flags */); + dest.writeTypedObject(screenSpaceBounds, 0 /* flags */); + dest.writeTypedObject(windowConfiguration, 0 /* flags */); dest.writeBoolean(isNotInRecents); - dest.writeParcelable(startLeash, 0 /* flags */); - dest.writeParcelable(startBounds, 0 /* flags */); - dest.writeParcelable(pictureInPictureParams, 0 /* flags */); + dest.writeTypedObject(startLeash, 0 /* flags */); + dest.writeTypedObject(startBounds, 0 /* flags */); + dest.writeTypedObject(pictureInPictureParams, 0 /* flags */); } public void dump(PrintWriter pw, String prefix) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ebef4646b0d9..ab7732b47ca4 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22188,9 +22188,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * and hardware acceleration. */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { - // Clear the overscroll effect: - // TODO: Use internal API instead of overriding the existing RenderEffect - setRenderEffect(null); final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList. diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index c2f17c310363..ec23a29b2070 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -310,6 +310,18 @@ public class ViewConfiguration { */ private static final float AMBIGUOUS_GESTURE_MULTIPLIER = 2f; + /** + * The timeout value in milliseconds to adjust the selection span and actions for the selected + * text when TextClassifier has been initialized. + */ + private static final int SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND = 200; + + /** + * The timeout value in milliseconds to adjust the selection span and actions for the selected + * text when TextClassifier has not been initialized. + */ + private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500; + private final boolean mConstructedWithContext; private final int mEdgeSlop; private final int mFadingEdgeLength; @@ -335,6 +347,8 @@ public class ViewConfiguration { private final float mHorizontalScrollFactor; private final boolean mShowMenuShortcutsWhenKeyboardPresent; private final long mScreenshotChordKeyTimeout; + private final int mSmartSelectionInitializedTimeout; + private final int mSmartSelectionInitializingTimeout; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768915) private boolean sHasPermanentMenuKey; @@ -378,6 +392,8 @@ public class ViewConfiguration { // Getter throws if mConstructedWithContext is false so doesn't matter what // this value is. mMinScalingSpan = 0; + mSmartSelectionInitializedTimeout = SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND; + mSmartSelectionInitializingTimeout = SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND; } /** @@ -488,6 +504,11 @@ public class ViewConfiguration { mScreenshotChordKeyTimeout = res.getInteger( com.android.internal.R.integer.config_screenshotChordKeyTimeout); + + mSmartSelectionInitializedTimeout = res.getInteger( + com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis); + mSmartSelectionInitializingTimeout = res.getInteger( + com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis); } /** @@ -1069,6 +1090,24 @@ public class ViewConfiguration { } /** + * @return the timeout value in milliseconds to adjust the selection span and actions for the + * selected text when TextClassifier has been initialized. + * @hide + */ + public int getSmartSelectionInitializedTimeout() { + return mSmartSelectionInitializedTimeout; + } + + /** + * @return the timeout value in milliseconds to adjust the selection span and actions for the + * selected text when TextClassifier has not been initialized. + * @hide + */ + public int getSmartSelectionInitializingTimeout() { + return mSmartSelectionInitializingTimeout; + } + + /** * @return the duration in milliseconds before an end of a long press causes a tooltip to be * hidden * @hide diff --git a/core/java/android/view/ViewFrameInfo.java b/core/java/android/view/ViewFrameInfo.java index d4aaa611f800..36bf53201e6f 100644 --- a/core/java/android/view/ViewFrameInfo.java +++ b/core/java/android/view/ViewFrameInfo.java @@ -17,6 +17,7 @@ package android.view; import android.graphics.FrameInfo; +import android.os.IInputConstants; /** * The timing information of events taking place in ViewRootImpl @@ -24,32 +25,14 @@ import android.graphics.FrameInfo; */ public class ViewFrameInfo { public long drawStart; - public long oldestInputEventTime; // the time of the oldest input event consumed for this frame - public long newestInputEventTime; // the time of the newest input event consumed for this frame + + // Various flags set to provide extra metadata about the current frame. See flag definitions // inside FrameInfo. // @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED public long flags; - /** - * Update the oldest event time. - * @param eventTime the time of the input event - */ - public void updateOldestInputEvent(long eventTime) { - if (oldestInputEventTime == 0 || eventTime < oldestInputEventTime) { - oldestInputEventTime = eventTime; - } - } - - /** - * Update the newest event time. - * @param eventTime the time of the input event - */ - public void updateNewestInputEvent(long eventTime) { - if (newestInputEventTime == 0 || eventTime > newestInputEventTime) { - newestInputEventTime = eventTime; - } - } + private int mInputEventId; /** * Populate the missing fields using the data from ViewFrameInfo @@ -58,8 +41,7 @@ public class ViewFrameInfo { public void populateFrameInfo(FrameInfo frameInfo) { frameInfo.frameInfo[FrameInfo.FLAGS] |= flags; frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart; - // TODO(b/169866723): Use InputEventAssigner - frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = newestInputEventTime; + frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = mInputEventId; } /** @@ -67,8 +49,7 @@ public class ViewFrameInfo { */ public void reset() { drawStart = 0; - oldestInputEventTime = 0; - newestInputEventTime = 0; + mInputEventId = IInputConstants.INVALID_INPUT_EVENT_ID; flags = 0; } @@ -78,4 +59,12 @@ public class ViewFrameInfo { public void markDrawStart() { drawStart = System.nanoTime(); } + + /** + * Assign the value for input event id + * @param eventId the id of the input event + */ + public void setInputEvent(int eventId) { + mInputEventId = eventId; + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f8e65bd0d056..390e3ae78143 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -457,6 +457,7 @@ public final class ViewRootImpl implements ViewParent, FallbackEventHandler mFallbackEventHandler; final Choreographer mChoreographer; protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo(); + private final InputEventAssigner mInputEventAssigner = new InputEventAssigner(); /** * Update the Choreographer's FrameInfo object with the timing information for the current @@ -8352,16 +8353,7 @@ public final class ViewRootImpl implements ViewParent, Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName, mPendingInputEventCount); - long eventTime = q.mEvent.getEventTimeNano(); - long oldestEventTime = eventTime; - if (q.mEvent instanceof MotionEvent) { - MotionEvent me = (MotionEvent)q.mEvent; - if (me.getHistorySize() > 0) { - oldestEventTime = me.getHistoricalEventTimeNano(0); - } - } - mViewFrameInfo.updateOldestInputEvent(oldestEventTime); - mViewFrameInfo.updateNewestInputEvent(eventTime); + mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent)); deliverInputEvent(q); } @@ -8497,6 +8489,11 @@ public final class ViewRootImpl implements ViewParent, consumedBatches = false; } doProcessInputEvents(); + if (consumedBatches) { + // Must be done after we processed the input events, to mark the completion of the frame + // from the input point of view + mInputEventAssigner.onChoreographerCallback(); + } return consumedBatches; } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 221b3346df58..cf5ec8de0362 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1716,13 +1716,22 @@ public abstract class Window { * For the blur region to be visible, the window has to be translucent. See * {@link android.R.styleable#Window_windowIsTranslucent}. * - * Note the difference with {@link android.view.WindowManager.LayoutParams#blurBehindRadius}, + * Note the difference with {@link WindowManager.LayoutParams#setBlurBehindRadius}, * which blurs the whole screen behind the window. Background blur blurs the screen behind * only within the bounds of the window. * + * Some devices might not support cross-window blur 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, resulting in a transparent window background. To avoid this, the app might want to + * change its theme to one that does not use blurs. To listen for cross-window blur + * enabled/disabled events, use {@link WindowManager#addCrossWindowBlurEnabledListener}. + * * @param blurRadius The blur radius to use for window background blur in pixels * * @see android.R.styleable#Window_windowBackgroundBlurRadius + * @see WindowManager.LayoutParams#setBlurBehindRadius + * @see WindowManager#addCrossWindowBlurEnabledListener */ public void setBackgroundBlurRadius(int blurRadius) {} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 9e87c95a8a75..7e9a850178fc 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -82,6 +82,7 @@ import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -120,6 +121,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; /** * The interface that apps use to talk to the window manager. @@ -611,6 +613,10 @@ public interface WindowManager extends ViewManager { * For example, for activities in multi-window mode, the metrics returned are based on the * current bounds that the user has selected for the {@link android.app.Activity Activity}'s * task. + * <p> + * In most scenarios, {@link #getCurrentWindowMetrics()} rather than + * {@link #getMaximumWindowMetrics()} is the correct API to use, since it ensures values reflect + * window size when the app is not fullscreen. * * @see #getMaximumWindowMetrics() * @see WindowMetrics @@ -622,26 +628,27 @@ public interface WindowManager extends ViewManager { /** * Returns the largest {@link WindowMetrics} an app may expect in the current system state. * <p> - * The metrics describe the size of the largest potential area the window might occupy with - * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets} - * such a window would have. - * <p> * The value of this is based on the largest <b>potential</b> windowing state of the system. * * For example, for activities in multi-window mode, the metrics returned are based on the * what the bounds would be if the user expanded the {@link android.app.Activity Activity}'s * task to cover the entire screen. - * + * <p> + * The metrics describe the size of the largest potential area the window might occupy with + * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets} + * such a window would have. + * <p> * Note that this might still be smaller than the size of the physical display if certain areas * of the display are not available to windows created in this {@link Context}. - * <p> + * * For example, given that there's a device which have a multi-task mode to limit activities * to a half screen. In this case, {@link #getMaximumWindowMetrics()} reports the bounds of - * the half screen which the activity is located, while {@link Display#getRealSize(Point)} still - * reports the bounds of the whole physical display. - * - * Despite this, {@link #getMaximumWindowMetrics()} and {@link Display#getRealSize(Point)} - * reports the same bounds in general. + * the half screen which the activity is located. + * <p> + * <b>Generally {@link #getCurrentWindowMetrics()} is the correct API to use</b> for choosing + * UI layouts. {@link #getMaximumWindowMetrics()} are only appropriate when the application + * needs to know the largest possible size it can occupy if the user expands/maximizes it on the + * screen. * * @see #getCurrentWindowMetrics() * @see WindowMetrics @@ -808,6 +815,64 @@ public interface WindowManager extends ViewManager { return DISPLAY_IME_POLICY_FALLBACK_DISPLAY; } + /** + * Returns whether cross-window blur is currently enabled. 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. To listen for cross-window blur + * enabled/disabled events, use {@link #addCrossWindowBlurEnabledListener}. + * + * @see #addCrossWindowBlurEnabledListener + * @see LayoutParams#setBlurBehindRadius + * @see Window#setBackgroundBlurRadius + */ + default boolean isCrossWindowBlurEnabled() { + return false; + } + + /** + * 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. + * + * The listener will be called on the main thread. + * + * If the listener is added successfully, it will be called immediately with the current + * cross-window blur enabled state. + * + * + * @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 Consumer<Boolean> listener) { + } + + /** + * Removes a listener, previously added with {@link #addCrossWindowBlurEnabledListener} + * + * @param listener the listener to be removed + * + * @see #addCrossWindowBlurEnabledListener + */ + default void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { + } + public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { /** * X position for this window. With the default gravity it is ignored. @@ -2722,6 +2787,15 @@ public interface WindowManager extends ViewManager { public boolean hasManualSurfaceInsets; /** + * Whether we should use global insets state when report insets to the window. When set to + * {@code true}, all the insets will be reported to the window regardless of the z-order. + * Otherwise, only the insets above the given window will be reported. + * + * @hide + */ + public boolean receiveInsetsIgnoringZOrder; + + /** * Whether the previous surface insets should be used vs. what is currently set. When set * to {@code true}, the view root will ignore surfaces insets in this object and use what * it currently has. @@ -3238,9 +3312,9 @@ public interface WindowManager extends ViewManager { * The blur behind radius range starts at 0, which means no blur, and increases until 150 * for the densest blur. * - * @see #FLAG_BLUR_BEHIND + * @see #setBlurBehindRadius */ - public int blurBehindRadius = 0; + private int mBlurBehindRadius = 0; /** * The color mode requested by this window. The target display may @@ -3534,6 +3608,50 @@ public interface WindowManager extends ViewManager { return mColorMode; } + /** + * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount}, + * but instead of dimmed, the content behind the window will be blurred (or combined with + * the dim amount, if such is specified). + * + * The density of the blur is set by the blur radius. The radius defines the size + * of the neighbouring area, from which pixels will be averaged to form the final + * color for each pixel. The operation approximates a Gaussian blur. + * A radius of 0 means no blur. The higher the radius, the denser the blur. + * + * Note the difference with {@link android.view.Window#setBackgroundBlurRadius}, + * which blurs only within the bounds of the window. Blur behind blurs the whole screen + * behind the window. + * + * Requires {@link #FLAG_BLUR_BEHIND} to be set. + * + * 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, resulting in there being no depth separation between the window + * and the content behind it. To avoid this, the app might want to use more + * {@link #dimAmount} on its window. To listen for cross-window blur enabled/disabled + * events, use {@link #addCrossWindowBlurEnabledListener}. + * + * @param blurBehindRadius The blur radius to use for blur behind in pixels + * + * @see #FLAG_BLUR_BEHIND + * @see #getBlurBehindRadius + * @see WindowManager#addCrossWindowBlurEnabledListener + * @see Window#setBackgroundBlurRadius + */ + public void setBlurBehindRadius(@IntRange(from = 0) int blurBehindRadius) { + mBlurBehindRadius = blurBehindRadius; + } + + /** + * Returns the blur behind radius of the window. + * + * @see #setBlurBehindRadius + */ + public int getBlurBehindRadius() { + return mBlurBehindRadius; + } + /** @hide */ @SystemApi public final void setUserActivityTimeout(long timeout) { @@ -3610,15 +3728,16 @@ public interface WindowManager extends ViewManager { out.writeInt(preferredDisplayModeId); out.writeInt(systemUiVisibility); out.writeInt(subtreeSystemUiVisibility); - out.writeInt(hasSystemUiListeners ? 1 : 0); + out.writeBoolean(hasSystemUiListeners); out.writeInt(inputFeatures); out.writeLong(userActivityTimeout); out.writeInt(surfaceInsets.left); out.writeInt(surfaceInsets.top); out.writeInt(surfaceInsets.right); out.writeInt(surfaceInsets.bottom); - out.writeInt(hasManualSurfaceInsets ? 1 : 0); - out.writeInt(preservePreviousSurfaceInsets ? 1 : 0); + out.writeBoolean(hasManualSurfaceInsets); + out.writeBoolean(receiveInsetsIgnoringZOrder); + out.writeBoolean(preservePreviousSurfaceInsets); out.writeLong(accessibilityIdOfAnchor); TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags); out.writeInt(mColorMode); @@ -3629,7 +3748,7 @@ public interface WindowManager extends ViewManager { out.writeInt(mFitInsetsSides); out.writeBoolean(mFitInsetsIgnoringVisibility); out.writeBoolean(preferMinimalPostProcessing); - out.writeInt(blurBehindRadius); + out.writeInt(mBlurBehindRadius); if (providesInsetsTypes != null) { out.writeInt(providesInsetsTypes.length); out.writeIntArray(providesInsetsTypes); @@ -3679,15 +3798,16 @@ public interface WindowManager extends ViewManager { preferredDisplayModeId = in.readInt(); systemUiVisibility = in.readInt(); subtreeSystemUiVisibility = in.readInt(); - hasSystemUiListeners = in.readInt() != 0; + hasSystemUiListeners = in.readBoolean(); inputFeatures = in.readInt(); userActivityTimeout = in.readLong(); surfaceInsets.left = in.readInt(); surfaceInsets.top = in.readInt(); surfaceInsets.right = in.readInt(); surfaceInsets.bottom = in.readInt(); - hasManualSurfaceInsets = in.readInt() != 0; - preservePreviousSurfaceInsets = in.readInt() != 0; + hasManualSurfaceInsets = in.readBoolean(); + receiveInsetsIgnoringZOrder = in.readBoolean(); + preservePreviousSurfaceInsets = in.readBoolean(); accessibilityIdOfAnchor = in.readLong(); accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mColorMode = in.readInt(); @@ -3698,7 +3818,7 @@ public interface WindowManager extends ViewManager { mFitInsetsSides = in.readInt(); mFitInsetsIgnoringVisibility = in.readBoolean(); preferMinimalPostProcessing = in.readBoolean(); - blurBehindRadius = in.readInt(); + mBlurBehindRadius = in.readInt(); int insetsTypesLength = in.readInt(); if (insetsTypesLength > 0) { providesInsetsTypes = new int[insetsTypesLength]; @@ -3752,7 +3872,7 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED = 1 << 28; /** {@hide} */ - public static final int BACKGROUND_BLUR_RADIUS_CHANGED = 1 << 29; + public static final int BLUR_BEHIND_RADIUS_CHANGED = 1 << 29; // internal buffer to backup/restore parameters under compatibility mode. private int[] mCompatibilityParamsBackup = null; @@ -3916,6 +4036,11 @@ public interface WindowManager extends ViewManager { changes |= SURFACE_INSETS_CHANGED; } + if (receiveInsetsIgnoringZOrder != o.receiveInsetsIgnoringZOrder) { + receiveInsetsIgnoringZOrder = o.receiveInsetsIgnoringZOrder; + changes |= SURFACE_INSETS_CHANGED; + } + if (preservePreviousSurfaceInsets != o.preservePreviousSurfaceInsets) { preservePreviousSurfaceInsets = o.preservePreviousSurfaceInsets; changes |= SURFACE_INSETS_CHANGED; @@ -3943,9 +4068,9 @@ public interface WindowManager extends ViewManager { changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED; } - if (blurBehindRadius != o.blurBehindRadius) { - blurBehindRadius = o.blurBehindRadius; - changes |= BACKGROUND_BLUR_RADIUS_CHANGED; + if (mBlurBehindRadius != o.mBlurBehindRadius) { + mBlurBehindRadius = o.mBlurBehindRadius; + changes |= BLUR_BEHIND_RADIUS_CHANGED; } // This can't change, it's only set at window creation time. @@ -4104,6 +4229,9 @@ public interface WindowManager extends ViewManager { sb.append(" (!preservePreviousSurfaceInsets)"); } } + if (receiveInsetsIgnoringZOrder) { + sb.append(" receive insets ignoring z-order"); + } if (mColorMode != COLOR_MODE_DEFAULT) { sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode)); } @@ -4111,9 +4239,9 @@ public interface WindowManager extends ViewManager { sb.append(" preferMinimalPostProcessing="); sb.append(preferMinimalPostProcessing); } - if (blurBehindRadius != 0) { + if (mBlurBehindRadius != 0) { sb.append(" blurBehindRadius="); - sb.append(blurBehindRadius); + sb.append(mBlurBehindRadius); } sb.append(System.lineSeparator()); sb.append(prefix).append(" fl=").append( diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 23842b3a41ac..b39870738d68 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -40,6 +40,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import java.util.List; +import java.util.function.Consumer; /** * Provides low-level communication with the system window manager for @@ -301,4 +302,19 @@ public final class WindowManagerImpl implements WindowManager { throw e.rethrowFromSystemServer(); } } + + @Override + public boolean isCrossWindowBlurEnabled() { + return CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled(); + } + + @Override + public void addCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { + CrossWindowBlurListeners.getInstance().addListener(listener); + } + + @Override + public void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { + CrossWindowBlurListeners.getInstance().removeListener(listener); + } } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 7cc2db1c0dab..72d403e1f867 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -149,8 +149,15 @@ public class WindowlessWindowManager implements IWindowSession { if (((attrs.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) { try { - mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags, + if(mRealWm instanceof IWindowSession.Stub) { + mRealWm.grantInputChannel(displayId, + new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"), + window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type, + outInputChannel); + } else { + mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type, outInputChannel); + } } catch (RemoteException e) { Log.e(TAG, "Failed to grant input to surface: ", e); } @@ -293,8 +300,14 @@ public class WindowlessWindowManager implements IWindowSession { if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0 && state.mInputChannelToken != null) { try { - mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc, - attrs.flags, attrs.privateFlags, state.mInputRegion); + if(mRealWm instanceof IWindowSession.Stub) { + mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, + new SurfaceControl(sc, "WindowlessWindowManager.relayout"), + attrs.flags, attrs.privateFlags, state.mInputRegion); + } else { + mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc, + attrs.flags, attrs.privateFlags, state.mInputRegion); + } } catch (RemoteException e) { Log.e(TAG, "Failed to update surface input channel: ", e); } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index f63749be6df2..8d1271d7311c 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -23,7 +23,9 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -113,6 +115,8 @@ public final class AccessibilityInteractionClient private final Object mInstanceLock = new Object(); + private Handler mMainHandler; + private volatile int mInteractionId = -1; private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; @@ -123,6 +127,11 @@ public final class AccessibilityInteractionClient private Message mSameThreadMessage; + private int mInteractionIdWaitingForPrefetchResult; + private int mConnectionIdWaitingForPrefetchResult; + private String[] mPackageNamesForNextPrefetchResult; + private Runnable mPrefetchResultRunnable; + /** * @return The client for the current thread. */ @@ -197,6 +206,10 @@ public final class AccessibilityInteractionClient private AccessibilityInteractionClient() { /* reducing constructor visibility */ + Looper mainLooper = Looper.getMainLooper(); + if (mainLooper != null) { + mMainHandler = new Handler(mainLooper); + } } /** @@ -451,16 +464,16 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (packageNames != null) { - List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( - interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, - bypassCache, packageNames); - if (infos != null && !infos.isEmpty()) { - for (int i = 1; i < infos.size(); i++) { - infos.get(i).recycle(); - } - return infos.get(0); + AccessibilityNodeInfo info = + getFindAccessibilityNodeInfoResultAndClear(interactionId); + if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0 + && info != null) { + setInteractionWaitingForPrefetchResult(interactionId, connectionId, + packageNames); } + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + bypassCache, packageNames); + return info; } } else { if (DEBUG) { @@ -474,6 +487,15 @@ public final class AccessibilityInteractionClient return null; } + private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId, + String[] packageNames) { + synchronized (mInstanceLock) { + mInteractionIdWaitingForPrefetchResult = interactionId; + mConnectionIdWaitingForPrefetchResult = connectionId; + mPackageNamesForNextPrefetchResult = packageNames; + } + } + private static String idToString(int accessibilityWindowId, long accessibilityNodeId) { return accessibilityWindowId + "/" + AccessibilityNodeInfo.idToString(accessibilityNodeId); @@ -829,6 +851,59 @@ public final class AccessibilityInteractionClient } /** + * {@inheritDoc} + */ + @Override + public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos, + int interactionId) { + List<AccessibilityNodeInfo> infosCopy = null; + int mConnectionIdWaitingForPrefetchResultCopy = -1; + String[] mPackageNamesForNextPrefetchResultCopy = null; + + synchronized (mInstanceLock) { + if (!infos.isEmpty() && mInteractionIdWaitingForPrefetchResult == interactionId) { + if (mMainHandler != null) { + if (mPrefetchResultRunnable != null) { + mMainHandler.removeCallbacks(mPrefetchResultRunnable); + mPrefetchResultRunnable = null; + } + /** + * TODO(b/180957109): AccessibilityCache is prone to deadlocks + * We post caching the prefetched nodes in the main thread. Using the binder + * thread results in "Long monitor contention with owner main" logs where + * service response times may exceed 5 seconds. This is due to the cache calling + * out to the system when refreshing nodes with the lock held. + */ + mPrefetchResultRunnable = () -> finalizeAndCacheAccessibilityNodeInfos( + infos, mConnectionIdWaitingForPrefetchResult, false, + mPackageNamesForNextPrefetchResult); + mMainHandler.post(mPrefetchResultRunnable); + + } else { + for (AccessibilityNodeInfo info : infos) { + infosCopy.add(new AccessibilityNodeInfo(info)); + } + mConnectionIdWaitingForPrefetchResultCopy = + mConnectionIdWaitingForPrefetchResult; + mPackageNamesForNextPrefetchResultCopy = + new String[mPackageNamesForNextPrefetchResult.length]; + for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) { + mPackageNamesForNextPrefetchResultCopy[i] = + mPackageNamesForNextPrefetchResult[i]; + } + } + } + + } + + if (infosCopy != null) { + finalizeAndCacheAccessibilityNodeInfos( + infosCopy, mConnectionIdWaitingForPrefetchResultCopy, false, + mPackageNamesForNextPrefetchResultCopy); + } + } + + /** * Gets the result of a request to perform an accessibility action. * * @param interactionId The interaction id to match the result with the request. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 97ce92cd90aa..ab46170792a5 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1781,8 +1781,12 @@ public class AccessibilityNodeInfo implements Parcelable { * @param viewId The fully qualified resource name of the view id to find. * @return A list of node info. */ - public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) { + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String viewId) { enforceSealed(); + if (viewId == null) { + Log.e(TAG, "returns empty list due to null viewId."); + return Collections.emptyList(); + } if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) { return Collections.emptyList(); } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index 049bb31adbb1..231e75a19a06 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -47,6 +47,15 @@ oneway interface IAccessibilityInteractionConnectionCallback { int interactionId); /** + * Sets the result of a prefetch request that returns {@link AccessibilityNodeInfo}s. + * + * @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of. + * @param infos The result {@link AccessibilityNodeInfo}s. + */ + void setPrefetchAccessibilityNodeInfoResult( + in List<AccessibilityNodeInfo> infos, int interactionId); + + /** * Sets the result of a request to perform an accessibility action. * * @param Whether the action was performed. diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java index 9bf3626fe868..9998fbc02d12 100644 --- a/core/java/android/view/contentcapture/ContentCaptureContext.java +++ b/core/java/android/view/contentcapture/ContentCaptureContext.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.TaskInfo; +import android.app.assist.ActivityId; import android.content.ComponentName; import android.content.Context; import android.content.LocusId; @@ -36,6 +37,8 @@ import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + /** * Context associated with a {@link ContentCaptureSession} - see {@link ContentCaptureManager} for * more info. @@ -99,16 +102,17 @@ public final class ContentCaptureContext implements Parcelable { // Fields below are set by server when the session starts private final @Nullable ComponentName mComponentName; - private final int mTaskId; private final int mFlags; private final int mDisplayId; + private final ActivityId mActivityId; // Fields below are set by the service upon "delivery" and are not marshalled in the parcel private int mParentSessionId = NO_SESSION_ID; /** @hide */ public ContentCaptureContext(@Nullable ContentCaptureContext clientContext, - @NonNull ComponentName componentName, int taskId, int displayId, int flags) { + @NonNull ActivityId activityId, @NonNull ComponentName componentName, int displayId, + int flags) { if (clientContext != null) { mHasClientContext = true; mExtras = clientContext.mExtras; @@ -118,10 +122,10 @@ public final class ContentCaptureContext implements Parcelable { mExtras = null; mId = null; } - mComponentName = Preconditions.checkNotNull(componentName); - mTaskId = taskId; - mDisplayId = displayId; + mComponentName = Objects.requireNonNull(componentName); mFlags = flags; + mDisplayId = displayId; + mActivityId = activityId; } private ContentCaptureContext(@NonNull Builder builder) { @@ -130,8 +134,9 @@ public final class ContentCaptureContext implements Parcelable { mId = builder.mId; mComponentName = null; - mTaskId = mFlags = 0; + mFlags = 0; mDisplayId = Display.INVALID_DISPLAY; + mActivityId = null; } /** @hide */ @@ -140,9 +145,9 @@ public final class ContentCaptureContext implements Parcelable { mExtras = original.mExtras; mId = original.mId; mComponentName = original.mComponentName; - mTaskId = original.mTaskId; mFlags = original.mFlags | extraFlags; mDisplayId = original.mDisplayId; + mActivityId = original.mActivityId; } /** @@ -170,7 +175,7 @@ public final class ContentCaptureContext implements Parcelable { */ @SystemApi public int getTaskId() { - return mTaskId; + return mHasClientContext ? 0 : mActivityId.getTaskId(); } /** @@ -184,6 +189,18 @@ public final class ContentCaptureContext implements Parcelable { } /** + * Gets the Activity id information associated with this context, or {@code null} when it is a + * child session. + * + * @hide + */ + @SystemApi + @Nullable + public ActivityId getActivityId() { + return mHasClientContext ? null : mActivityId; + } + + /** * Gets the id of the session that originated this session (through * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}), * or {@code null} if this is the main session associated with the Activity's {@link Context}. @@ -309,7 +326,7 @@ public final class ContentCaptureContext implements Parcelable { if (mId != null) { pw.print(", id="); mId.dump(pw); } - pw.print(", taskId="); pw.print(mTaskId); + pw.print(", activityId="); pw.print(mActivityId); pw.print(", displayId="); pw.print(mDisplayId); if (mParentSessionId != NO_SESSION_ID) { pw.print(", parentId="); pw.print(mParentSessionId); @@ -333,7 +350,7 @@ public final class ContentCaptureContext implements Parcelable { if (fromServer()) { builder.append("act=").append(ComponentName.flattenToShortString(mComponentName)) - .append(", taskId=").append(mTaskId) + .append(", activityId=").append(mActivityId) .append(", displayId=").append(mDisplayId) .append(", flags=").append(mFlags); } else { @@ -363,9 +380,9 @@ public final class ContentCaptureContext implements Parcelable { } parcel.writeParcelable(mComponentName, flags); if (fromServer()) { - parcel.writeInt(mTaskId); parcel.writeInt(mDisplayId); parcel.writeInt(mFlags); + mActivityId.writeToParcel(parcel, flags); } } @@ -393,11 +410,12 @@ public final class ContentCaptureContext implements Parcelable { // Client-state only return clientContext; } else { - final int taskId = parcel.readInt(); final int displayId = parcel.readInt(); final int flags = parcel.readInt(); - return new ContentCaptureContext(clientContext, componentName, taskId, displayId, - flags); + final ActivityId activityId = new ActivityId(parcel); + + return new ContentCaptureContext(clientContext, activityId, componentName, + displayId, flags); } } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 46e0306cefc8..9523bcdb8e39 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -435,10 +435,11 @@ public final class ContentCaptureManager { /** @hide */ @UiThread public void onActivityCreated(@NonNull IBinder applicationToken, - @NonNull ComponentName activityComponent) { + @NonNull IBinder shareableActivityToken, @NonNull ComponentName activityComponent) { if (mOptions.lite) return; synchronized (mLock) { - getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags); + getMainContentCaptureSession().start(applicationToken, shareableActivityToken, + activityComponent, mFlags); } } diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index ef8295c30fdf..a64111069c9b 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -22,6 +22,7 @@ import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IDataShareWriteAdapter; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.os.IBinder; import android.os.ICancellationSignal; @@ -44,8 +45,9 @@ oneway interface IContentCaptureManager { * @param flags Meta flags that enable or disable content capture (see * {@link IContentCaptureContext#flags}). */ - void startSession(IBinder activityToken, in ComponentName componentName, - int sessionId, int flags, in IResultReceiver result); + void startSession(IBinder activityToken, IBinder shareableActivityToken, + in ComponentName componentName, int sessionId, int flags, + in IResultReceiver result); /** * Marks the end of a session for the calling user identified by @@ -100,4 +102,10 @@ oneway interface IContentCaptureManager { * Sets whether the default service should be used. */ void setDefaultServiceEnabled(int userId, boolean enabled); + + /** + * Registers a listener to handle updates ContentCaptureOptions from server. + */ + void registerContentCaptureOptionsCallback(String packageName, + in IContentCaptureOptionsCallback callback); } diff --git a/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl b/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl new file mode 100644 index 000000000000..b0f062de3bb9 --- /dev/null +++ b/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.contentcapture; + +import android.content.ContentCaptureOptions; + +/** + * Callback for changes to content capture options made by ContentCaptureService. + * Callback interface used by IContentCaptureManager to send asynchronous + * notifications back to its clients. Note that this is a + * one-way interface so the server does not block waiting for the client. + * + * @hide + */ +oneway interface IContentCaptureOptionsCallback { + void setContentCaptureOptions(in ContentCaptureOptions options); +} diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 3c18b6b89af8..5ca793e3c394 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -123,6 +123,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @Nullable private IBinder mApplicationToken; + @Nullable + private IBinder mShareableActivityToken; @Nullable private ComponentName mComponentName; @@ -217,8 +219,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * Starts this session. */ @UiThread - void start(@NonNull IBinder token, @NonNull ComponentName component, - int flags) { + void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags) { if (!isContentCaptureEnabled()) return; if (sVerbose) { @@ -237,6 +239,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mState = STATE_WAITING_FOR_SERVER; mApplicationToken = token; + mShareableActivityToken = shareableActivityToken; mComponentName = component; if (sVerbose) { @@ -245,8 +248,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } try { - mSystemServerInterface.startSession(mApplicationToken, component, mId, flags, - mSessionStateReceiver); + mSystemServerInterface.startSession(mApplicationToken, mShareableActivityToken, + component, mId, flags, mSessionStateReceiver); } catch (RemoteException e) { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } @@ -583,6 +586,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mDisabled.set((newState & STATE_DISABLED) != 0); // TODO(b/122454205): must reset children (which currently is owned by superclass) mApplicationToken = null; + mShareableActivityToken = null; mComponentName = null; mEvents = null; if (mDirectServiceInterface != null) { @@ -721,6 +725,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (mApplicationToken != null) { pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken); } + if (mShareableActivityToken != null) { + pw.print(prefix); pw.print("sharable activity token: "); + pw.println(mShareableActivityToken); + } if (mComponentName != null) { pw.print(prefix); pw.print("component name: "); pw.println(mComponentName.flattenToShortString()); diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java index 15b29adafddd..04d29ee3d48a 100644 --- a/core/java/android/view/displayhash/DisplayHashResultCallback.java +++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java @@ -66,12 +66,19 @@ public interface DisplayHashResultCallback { */ int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4; + /** + * The hash algorithm sent to generate the hash was invalid. This means the value is not one + * of the supported values in {@link DisplayHashManager#getSupportedHashAlgorithms()} + */ + int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; + /** @hide */ @IntDef(prefix = {"DISPLAY_HASH_ERROR_"}, value = { DISPLAY_HASH_ERROR_UNKNOWN, DISPLAY_HASH_ERROR_INVALID_BOUNDS, DISPLAY_HASH_ERROR_MISSING_WINDOW, - DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN + DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, + DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM }) @Retention(RetentionPolicy.SOURCE) @interface DisplayHashErrorCode { diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 2975afc084e6..d0959f97e438 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -90,6 +90,12 @@ public final class TextClassificationConstants { static final String SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND = "system_textclassifier_api_timeout_in_second"; + /** + * The max amount of characters before and after the selected text that are passed to the + * TextClassifier for the smart selection. + */ + private static final String SMART_SELECTION_TRIM_DELTA = "smart_selection_trim_delta"; + private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null; private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; @@ -100,6 +106,7 @@ public final class TextClassificationConstants { private static final boolean SMART_SELECT_ANIMATION_ENABLED_DEFAULT = true; private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000; private static final long SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT = 60; + private static final int SMART_SELECTION_TRIM_DELTA_DEFAULT = 120; @Nullable public String getTextClassifierServicePackageOverride() { @@ -155,6 +162,12 @@ public final class TextClassificationConstants { SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT); } + public int getSmartSelectionTrimDelta() { + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + SMART_SELECTION_TRIM_DELTA, + SMART_SELECTION_TRIM_DELTA_DEFAULT); + } + void dump(IndentingPrintWriter pw) { pw.println("TextClassificationConstants:"); pw.increaseIndent(); @@ -170,6 +183,7 @@ public final class TextClassificationConstants { getTextClassifierServicePackageOverride()).println(); pw.print(SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND, getSystemTextClassifierApiTimeoutInSecond()).println(); + pw.print(SMART_SELECTION_TRIM_DELTA, getSmartSelectionTrimDelta()).println(); pw.decreaseIndent(); } }
\ No newline at end of file diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index e1754531d761..872e15e3d43e 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -36,6 +36,10 @@ oneway interface ITranslationManager { int sessionId, in IResultReceiver receiver, int userId); void updateUiTranslationState(int state, in TranslationSpec sourceSpec, - in TranslationSpec destSpec, in List<AutofillId> viewIds, in int taskId, + in TranslationSpec destSpec, in List<AutofillId> viewIds, IBinder token, int taskId, + int userId); + // deprecated + void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec, + in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId, int userId); } diff --git a/core/java/android/view/translation/TranslationSpec.java b/core/java/android/view/translation/TranslationSpec.java index ab1bc477e0fd..16418a78f2c1 100644 --- a/core/java/android/view/translation/TranslationSpec.java +++ b/core/java/android/view/translation/TranslationSpec.java @@ -28,7 +28,7 @@ import com.android.internal.util.DataClass; * <p>This spec help specify information such as the language/locale for the translation, as well * as the data format for the translation (text, audio, etc.)</p> */ -@DataClass(genEqualsHashCode = true, genHiddenConstDefs = true) +@DataClass(genEqualsHashCode = true, genHiddenConstDefs = true, genToString = true) public final class TranslationSpec implements Parcelable { /** Data format for translation is text. */ @@ -99,6 +99,18 @@ public final class TranslationSpec implements Parcelable { @Override @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "TranslationSpec { " + + "language = " + mLanguage + ", " + + "dataFormat = " + mDataFormat + + " }"; + } + + @Override + @DataClass.Generated.Member public boolean equals(@android.annotation.Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(TranslationSpec other) { ... } @@ -175,10 +187,10 @@ public final class TranslationSpec implements Parcelable { }; @DataClass.Generated( - time = 1609964630624L, + time = 1614326090637L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/view/translation/TranslationSpec.java", - inputSignatures = "public static final int DATA_FORMAT_TEXT\nprivate final @android.annotation.NonNull java.lang.String mLanguage\nprivate final @android.view.translation.TranslationSpec.DataFormat int mDataFormat\nclass TranslationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genHiddenConstDefs=true)") + inputSignatures = "public static final int DATA_FORMAT_TEXT\nprivate final @android.annotation.NonNull java.lang.String mLanguage\nprivate final @android.view.translation.TranslationSpec.DataFormat int mDataFormat\nclass TranslationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genHiddenConstDefs=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java index 22c3e57ecc95..163f832882c5 100644 --- a/core/java/android/view/translation/Translator.java +++ b/core/java/android/view/translation/Translator.java @@ -35,6 +35,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; import com.android.internal.util.SyncResultReceiver; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -221,6 +222,12 @@ public class Translator { return mId; } + /** @hide */ + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + pw.print(prefix); pw.print("sourceSpec: "); pw.println(mSourceSpec); + pw.print(prefix); pw.print("destSpec: "); pw.println(mDestSpec); + } + /** * Requests a translation for the provided {@link TranslationRequest} using the Translator's * source spec and destination spec. diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index b49d3c004f44..81006121bdef 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -32,11 +32,15 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewRootImpl; +import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.translation.UiTranslationManager.UiTranslationState; import com.android.internal.util.function.pooled.PooledLambda; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -130,6 +134,23 @@ public class UiTranslationController { } /** + * Called to dump the translation information for Activity. + */ + public void dump(String outerPrefix, PrintWriter pw) { + pw.print(outerPrefix); pw.println("UiTranslationController:"); + final String pfx = outerPrefix + " "; + pw.print(pfx); pw.print("activity: "); pw.println(mActivity); + final int translatorSize = mTranslators.size(); + pw.print(outerPrefix); pw.print("number translator: "); pw.println(translatorSize); + for (int i = 0; i < translatorSize; i++) { + pw.print(outerPrefix); pw.print("#"); pw.println(i); + final Translator translator = mTranslators.valueAt(i); + translator.dump(outerPrefix, pw); + pw.println(); + } + } + + /** * The method is used by {@link Translator}, it will be called when the translation is done. The * translation result can be get from here. */ @@ -194,24 +215,19 @@ public class UiTranslationController { private void onUiTranslationStarted(Translator translator, List<AutofillId> views) { synchronized (mLock) { // Find Views collect the translation data - // TODO(b/178084101): try to optimize, e.g. to this in a single traversal - final int viewCounts = views.size(); final ArrayList<TranslationRequest> requests = new ArrayList<>(); - for (int i = 0; i < viewCounts; i++) { - final AutofillId viewAutofillId = views.get(i); - final View view = mActivity.findViewByAutofillIdTraversal(viewAutofillId); - if (view == null) { - Log.w(TAG, "Can not find the View for autofill id= " + viewAutofillId); - continue; - } - mViews.put(viewAutofillId, new WeakReference<>(view)); + final ArrayList<View> foundViews = new ArrayList<>(); + findViewsTraversalByAutofillIds(views, foundViews); + for (int i = 0; i < foundViews.size(); i++) { + final View view = foundViews.get(i); + final int currentCount = i; mActivity.runOnUiThread(() -> { final TranslationRequest translationRequest = view.onCreateTranslationRequest(); if (translationRequest != null && translationRequest.getTranslationText().length() > 0) { requests.add(translationRequest); } - if (requests.size() == viewCounts) { + if (currentCount == (foundViews.size() - 1)) { Log.v(TAG, "onUiTranslationStarted: send " + requests.size() + " request."); mWorkerHandler.sendMessage(PooledLambda.obtainMessage( UiTranslationController::sendTranslationRequest, @@ -222,6 +238,42 @@ public class UiTranslationController { } } + private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds, + ArrayList<View> foundViews) { + final ArrayList<ViewRootImpl> roots = + WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken()); + for (int rootNum = 0; rootNum < roots.size(); rootNum++) { + final View rootView = roots.get(rootNum).getView(); + if (rootView instanceof ViewGroup) { + findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds, foundViews); + } else { + addViewIfNeeded(sourceViewIds, rootView, foundViews); + } + } + } + + private void findViewsTraversalByAutofillIds(ViewGroup viewGroup, + List<AutofillId> sourceViewIds, ArrayList<View> foundViews) { + final int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; ++i) { + final View child = viewGroup.getChildAt(i); + if (child instanceof ViewGroup) { + findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds, foundViews); + } else { + addViewIfNeeded(sourceViewIds, child, foundViews); + } + } + } + + private void addViewIfNeeded(List<AutofillId> sourceViewIds, View view, + ArrayList<View> foundViews) { + final AutofillId autofillId = view.getAutofillId(); + if (sourceViewIds.contains(autofillId)) { + mViews.put(autofillId, new WeakReference<>(view)); + foundViews.add(view); + } + } + private void runForEachView(Consumer<View> action) { synchronized (mLock) { final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews); diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index eeb463ae0ed3..a3a6a2e52138 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -20,6 +20,7 @@ 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.RemoteException; import android.view.View; @@ -95,26 +96,61 @@ public final class UiTranslationManager { /** * Request ui translation for a given Views. * + * NOTE: Please use {@code startTranslation(TranslationSpec, TranslationSpec, List<AutofillId>, + * ActivityId)} instead. + * * @param sourceSpec {@link TranslationSpec} for the data to be translated. * @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 */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, int taskId) { - // TODO(b/177789967): Return result code or find a way to notify the status. - // TODO(b/177394471): The is a temparary API, the expected is requestUiTranslation( - // TranslationSpec, TranslationSpec,List<AutofillId>, Binder). We may need more time to - // implement it, use task id as initial version for demo. Objects.requireNonNull(sourceSpec); Objects.requireNonNull(destSpec); Objects.requireNonNull(viewIds); + if (viewIds.size() == 0) { + throw new IllegalArgumentException("Invalid empty views: " + viewIds); + } + try { + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_STARTED, sourceSpec, + destSpec, viewIds, taskId, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** + * Request ui translation for a given Views. + * + * @param sourceSpec {@link TranslationSpec} for the data to be translated. + * @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 activityId the identifier for the Activity which needs ui translation + * @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} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void startTranslation(@NonNull TranslationSpec sourceSpec, + @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, + @NonNull ActivityId activityId) { + // TODO(b/177789967): Return result code or find a way to notify the status. + Objects.requireNonNull(sourceSpec); + Objects.requireNonNull(destSpec); + Objects.requireNonNull(viewIds); + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + if (viewIds.size() == 0) { + throw new IllegalArgumentException("Invalid empty views: " + viewIds); + } try { mService.updateUiTranslationState(STATE_UI_TRANSLATION_STARTED, sourceSpec, - destSpec, viewIds, taskId, mContext.getUserId()); + destSpec, viewIds, activityId.getToken(), activityId.getTaskId(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -124,14 +160,15 @@ public final class UiTranslationManager { * Request to disable the ui translation. It will destroy all the {@link Translator}s and no * longer to show to show the translated text. * + * NOTE: Please use {@code finishTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is finishUiTranslation( - // Binder). We may need more time to implement it, use task id as initial version. - mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED, + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { @@ -140,17 +177,39 @@ public final class UiTranslationManager { } /** + * Request to disable the ui translation. It will destroy all the {@link Translator}s and no + * longer to show to show the translated text. + * + * @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} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void finishTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Request to pause the current ui translation's {@link Translator} which will switch back to * the original language. * + * NOTE: Please use {@code pauseTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is pauseUiTranslation(Binder) - // We may need more time to implement it, use task id as initial version for demo - mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED, + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { @@ -159,21 +218,64 @@ public final class UiTranslationManager { } /** + * Request to pause the current ui translation's {@link Translator} which will switch back to + * the original language. + * + * @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} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void pauseTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Request to resume the paused ui translation's {@link Translator} which will switch to the * translated language if the text had been translated. * + * NOTE: Please use {@code resumeTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is resumeUiTranslation( - // Binder). We may need more time to implement it, use task id as initial version. - mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED, + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + + /** + * Request to resume the paused ui translation's {@link Translator} which will switch to the + * translated language if the text had been translated. + * + * @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} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void resumeTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 1b62266c12e2..dc42ad583543 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -29,7 +29,6 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RecordingCanvas; import android.graphics.Rect; -import android.graphics.RenderEffect; import android.graphics.RenderNode; import android.os.Build; import android.util.AttributeSet; @@ -83,6 +82,8 @@ public class EdgeEffect { public @interface EdgeEffectType { } + private static final float DEFAULT_MAX_STRETCH_INTENSITY = 1.5f; + @SuppressWarnings("UnusedDeclaration") private static final String TAG = "EdgeEffect"; @@ -128,6 +129,8 @@ public class EdgeEffect { private long mStartTime; private float mDuration; + private float mStretchIntensity = DEFAULT_MAX_STRETCH_INTENSITY; + private float mStretchDistance = -1f; private final Interpolator mInterpolator; @@ -146,6 +149,8 @@ public class EdgeEffect { private float mPullDistance; private final Rect mBounds = new Rect(); + private float mWidth; + private float mHeight; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769450) private final Paint mPaint = new Paint(); private float mRadius; @@ -202,6 +207,19 @@ public class EdgeEffect { mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f; mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h)); + + mWidth = width; + mHeight = height; + } + + /** + * Configure the distance in pixels to stretch the content. This is only consumed as part + * if {@link #setType(int)} is set to {@link #TYPE_STRETCH} + * @param stretchDistance Stretch distance in pixels when the target View is overscrolled + * @hide + */ + public void setStretchDistance(float stretchDistance) { + mStretchDistance = stretchDistance; } /** @@ -437,6 +455,13 @@ public class EdgeEffect { } /** + * @hide + */ + public void setMaxStretchIntensity(float stretchIntensity) { + mStretchIntensity = stretchIntensity; + } + + /** * Set or clear the blend mode. A blend mode defines how source pixels * (generated by a drawing command) are composited with the destination pixels * (content of the render target). @@ -520,23 +545,55 @@ public class EdgeEffect { RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; if (mTmpMatrix == null) { mTmpMatrix = new Matrix(); - mTmpPoints = new float[4]; + mTmpPoints = new float[12]; } //noinspection deprecation recordingCanvas.getMatrix(mTmpMatrix); - mTmpPoints[0] = mBounds.width() * mDisplacement; - mTmpPoints[1] = mDistance * mBounds.height(); - mTmpPoints[2] = mTmpPoints[0]; - mTmpPoints[3] = 0; + + mTmpPoints[0] = 0; + mTmpPoints[1] = 0; // top-left + mTmpPoints[2] = mWidth; + mTmpPoints[3] = 0; // top-right + mTmpPoints[4] = mWidth; + mTmpPoints[5] = mHeight; // bottom-right + mTmpPoints[6] = 0; + mTmpPoints[7] = mHeight; // bottom-left + mTmpPoints[8] = mWidth * mDisplacement; + mTmpPoints[9] = 0; // drag start point + mTmpPoints[10] = mWidth * mDisplacement; + mTmpPoints[11] = mHeight * mDistance; // drag point mTmpMatrix.mapPoints(mTmpPoints); - float x = mTmpPoints[0] - mTmpPoints[2]; - float y = mTmpPoints[1] - mTmpPoints[3]; RenderNode renderNode = recordingCanvas.mNode; - // TODO: use stretchy RenderEffect and use internal API when it is ready - // TODO: wrap existing RenderEffect - renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y)); + float left = renderNode.getLeft() + + min(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]); + float top = renderNode.getTop() + + min(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]); + float right = renderNode.getLeft() + + max(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]); + float bottom = renderNode.getTop() + + max(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]); + // assume rotations of increments of 90 degrees + float x = mTmpPoints[10] - mTmpPoints[8]; + float width = right - left; + float vecX = Math.max(-1f, Math.min(1f, x / width)); + float y = mTmpPoints[11] - mTmpPoints[9]; + float height = bottom - top; + float vecY = Math.max(-1f, Math.min(1f, y / height)); + renderNode.stretch( + left, + top, + right, + bottom, + vecX * mStretchIntensity, + vecY * mStretchIntensity, + // TODO (njawad/mount) figure out proper stretch distance from UX + // 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)) + ); } boolean oneLastFrame = false; @@ -548,6 +605,18 @@ public class EdgeEffect { return mState != STATE_IDLE || oneLastFrame; } + private float min(float f1, float f2, float f3, float f4) { + float min = Math.min(f1, f2); + min = Math.min(min, f3); + return Math.min(min, f4); + } + + private float max(float f1, float f2, float f3, float f4) { + float max = Math.max(f1, f2); + max = Math.max(max, f3); + return Math.max(max, f4); + } + /** * Return the maximum height that the edge effect will be drawn at given the original * {@link #setSize(int, int) input size}. diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 23915e06335a..bf552e2a501c 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -249,6 +249,26 @@ public class HorizontalScrollView extends FrameLayout { } /** + * API used for prototyping stretch effect parameters in framework sample apps + * @hide + */ + public void setEdgeEffectIntensity(float intensity) { + mEdgeGlowLeft.setMaxStretchIntensity(intensity); + mEdgeGlowRight.setMaxStretchIntensity(intensity); + invalidate(); + } + + /** + * API used for prototyping stretch effect parameters in the framework sample apps + * @hide + */ + public void setStretchDistance(float distance) { + mEdgeGlowLeft.setStretchDistance(distance); + mEdgeGlowRight.setStretchDistance(distance); + invalidate(); + } + + /** * Sets the right edge effect color. * * @param color The color for the right edge effect. diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e0b4ec71b0a0..dfb2263a97a7 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -51,7 +51,6 @@ import android.content.res.loader.ResourcesProvider; import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Outline; -import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -76,6 +75,7 @@ import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; import android.util.Pair; +import android.util.SizeF; import android.util.SparseIntArray; import android.util.TypedValue; import android.util.TypedValue.ComplexDimensionUnit; @@ -353,7 +353,7 @@ public class RemoteViews implements Parcelable, Filter { * Only to be used on children views used in a {@link RemoteViews} with * {@link RemoteViews#hasSizedRemoteViews()}. */ - private PointF mIdealSize = null; + private SizeF mIdealSize = null; @ApplyFlags private int mApplyFlags = 0; @@ -705,6 +705,14 @@ public class RemoteViews implements Parcelable, Filter { public String getPackageName() { return mContextForResources.getPackageName(); } + + @Override + public boolean isRestricted() { + // Override isRestricted and direct to resource's implementation. The isRestricted is + // used for determining the risky resources loading, e.g. fonts, thus direct to context + // for resource. + return mContextForResources.isRestricted(); + } } private class SetEmptyView extends Action { @@ -3034,11 +3042,11 @@ public class RemoteViews implements Parcelable, Filter { return mSizedRemoteViews != null; } - private @Nullable PointF getIdealSize() { + private @Nullable SizeF getIdealSize() { return mIdealSize; } - private void setIdealSize(@Nullable PointF size) { + private void setIdealSize(@Nullable SizeF size) { mIdealSize = size; } @@ -3086,13 +3094,18 @@ public class RemoteViews implements Parcelable, Filter { * Create a new RemoteViews object that will inflate the layout with the closest size * specification. * - * The default remote views in that case is always the smallest one provided. + * The default remote views in that case is always the one with the smallest area. + * + * If the {@link RemoteViews} host provides the size of the view, the layout with the largest + * area that fits entirely in the provided size will be used (i.e. the width and height of + * the layout must be less than the size of the view, with a 1dp margin to account for + * rounding). If no layout fits in the view, the layout with the smallest area will be used. * * @param remoteViews Mapping of size to layout. * @throws IllegalArgumentException if the map is empty, there are more than * MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application. */ - public RemoteViews(@NonNull Map<PointF, RemoteViews> remoteViews) { + public RemoteViews(@NonNull Map<SizeF, RemoteViews> remoteViews) { if (remoteViews.isEmpty()) { throw new IllegalArgumentException("The set of RemoteViews cannot be empty"); } @@ -3127,8 +3140,8 @@ public class RemoteViews implements Parcelable, Filter { RemoteViews smallestView = null; while (remoteViews.hasNext()) { RemoteViews view = remoteViews.next(); - PointF size = view.getIdealSize(); - float newViewArea = size.x * size.y; + SizeF size = view.getIdealSize(); + float newViewArea = size.getWidth() * size.getHeight(); if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) { throw new IllegalArgumentException( "All RemoteViews must share the same package and user"); @@ -3231,7 +3244,7 @@ public class RemoteViews implements Parcelable, Filter { if (mode == MODE_NORMAL) { mApplication = parcel.readInt() == 0 ? info : ApplicationInfo.CREATOR.createFromParcel(parcel); - mIdealSize = parcel.readInt() == 0 ? null : PointF.CREATOR.createFromParcel(parcel); + mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel); mLayoutId = parcel.readInt(); mLightBackgroundLayoutId = parcel.readInt(); @@ -4617,9 +4630,9 @@ public class RemoteViews implements Parcelable, Filter { * * This is particularly useful when we only care about the ordering of the distances. */ - private static float squareDistance(PointF p1, PointF p2) { - float dx = p1.x - p2.x; - float dy = p1.y - p2.y; + private static float squareDistance(SizeF p1, SizeF p2) { + float dx = p1.getWidth() - p2.getWidth(); + float dy = p1.getHeight() - p2.getHeight(); return dx * dx + dy * dy; } @@ -4629,31 +4642,17 @@ public class RemoteViews implements Parcelable, Filter { * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions * are smaller than the ones of the widget, adding some padding to account for rounding errors. */ - private static boolean fitsIn(PointF sizeLayout, @Nullable PointF sizeWidget) { - return sizeWidget != null && (Math.ceil(sizeWidget.x) + 1 > sizeLayout.x) - && (Math.ceil(sizeWidget.y) + 1 > sizeLayout.y); + private static boolean fitsIn(SizeF sizeLayout, @Nullable SizeF sizeWidget) { + return sizeWidget != null && (Math.ceil(sizeWidget.getWidth()) + 1 > sizeLayout.getWidth()) + && (Math.ceil(sizeWidget.getHeight()) + 1 > sizeLayout.getHeight()); } - /** - * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the - * size of the widget. - * - * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is - * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the - * diagonal the most similar to the widget. If no layout fits or the size of the widget is - * not specified, the one with the smallest area will be chosen. - */ - private RemoteViews getRemoteViewsToApply(@NonNull Context context, - @Nullable PointF widgetSize) { - if (!hasSizedRemoteViews()) { - // If there isn't multiple remote views, fall back on the previous methods. - return getRemoteViewsToApply(context); - } + private RemoteViews findBestFitLayout(@NonNull SizeF widgetSize) { // Find the better remote view RemoteViews bestFit = null; float bestSqDist = Float.MAX_VALUE; for (RemoteViews layout : mSizedRemoteViews) { - PointF layoutSize = layout.getIdealSize(); + SizeF layoutSize = layout.getIdealSize(); if (fitsIn(layoutSize, widgetSize)) { if (bestFit == null) { bestFit = layout; @@ -4674,6 +4673,46 @@ public class RemoteViews implements Parcelable, Filter { return bestFit; } + /** + * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the + * size of the widget. + * + * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is + * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the + * diagonal the most similar to the widget. If no layout fits or the size of the widget is + * not specified, the one with the smallest area will be chosen. + * + * @hide + */ + public RemoteViews getRemoteViewsToApply(@NonNull Context context, + @Nullable SizeF widgetSize) { + if (!hasSizedRemoteViews()) { + // If there isn't multiple remote views, fall back on the previous methods. + return getRemoteViewsToApply(context); + } + return findBestFitLayout(widgetSize); + } + + /** + * Checks whether the change of size will lead to using a different {@link RemoteViews}. + * + * @hide + */ + @Nullable + public RemoteViews getRemoteViewsToApplyIfDifferent(@Nullable SizeF oldSize, + @NonNull SizeF newSize) { + if (!hasSizedRemoteViews()) { + return null; + } + RemoteViews oldBestFit = oldSize == null ? findSmallestRemoteView() : findBestFitLayout( + oldSize); + RemoteViews newBestFit = findBestFitLayout(newSize); + if (oldBestFit != newBestFit) { + return newBestFit; + } + return null; + } + /** * Inflates the view hierarchy represented by this object and applies @@ -4697,7 +4736,7 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public View apply(@NonNull Context context, @NonNull ViewGroup parent, - @Nullable InteractionHandler handler, @Nullable PointF size) { + @Nullable InteractionHandler handler, @Nullable SizeF size) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); View result = inflateView(context, rvToApply, parent); @@ -4714,7 +4753,7 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, @Nullable InteractionHandler handler, @StyleRes int applyThemeResId, - @Nullable PointF size) { + @Nullable SizeF size) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); View result = inflateView(context, rvToApply, parent, applyThemeResId, null); @@ -4724,7 +4763,7 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public View apply(Context context, ViewGroup parent, InteractionHandler handler, - @NonNull PointF size, @Nullable ColorResources colorResources) { + @NonNull SizeF size, @Nullable ColorResources colorResources) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); View result = inflateView(context, rvToApply, parent, 0, colorResources); @@ -4820,21 +4859,21 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, - PointF size) { + SizeF size) { return getAsyncApplyTask(context, parent, listener, handler, size, null /* themeColors */) .startTaskOnExecutor(executor); } /** @hide */ public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, - OnViewAppliedListener listener, InteractionHandler handler, PointF size, + OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { return getAsyncApplyTask(context, parent, listener, handler, size, colorResources) .startTaskOnExecutor(executor); } private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, - OnViewAppliedListener listener, InteractionHandler handler, PointF size, + OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, handler, colorResources, null /* result */); @@ -4960,7 +4999,7 @@ public class RemoteViews implements Parcelable, Filter { } /** @hide */ - public void reapply(Context context, View v, InteractionHandler handler, PointF size, + public void reapply(Context context, View v, InteractionHandler handler, SizeF size, ColorResources colorResources) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); @@ -5004,7 +5043,7 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public CancellationSignal reapplyAsync(Context context, View v, Executor executor, - OnViewAppliedListener listener, InteractionHandler handler, PointF size, + OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 65f3da79afe0..30067296f967 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -281,6 +281,26 @@ public class ScrollView extends FrameLayout { } /** + * API used for prototyping stretch effect parameters in framework sample apps + * @hide + */ + public void setEdgeEffectIntensity(float intensity) { + mEdgeGlowTop.setMaxStretchIntensity(intensity); + mEdgeGlowBottom.setMaxStretchIntensity(intensity); + invalidate(); + } + + /** + * API used for prototyping stretch effect parameters in the framework sample apps + * @hide + */ + public void setStretchDistance(float distance) { + mEdgeGlowTop.setStretchDistance(distance); + mEdgeGlowBottom.setStretchDistance(distance); + invalidate(); + } + + /** * Sets the bottom edge effect color. * * @param color The color for the bottom edge effect. diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 6f189204434a..eb6bce4a2f59 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -36,6 +36,7 @@ import android.text.TextUtils; import android.text.util.Linkify; import android.util.Log; import android.view.ActionMode; +import android.view.ViewConfiguration; import android.view.textclassifier.ExtrasUtils; import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.SelectionEvent.InvocationMethod; @@ -1056,10 +1057,12 @@ public final class SelectionActionModeHelper { */ private static final class TextClassificationHelper { - private static final int TRIM_DELTA = 120; // characters + // The fixed upper bound of context size. + private static final int TRIM_DELTA_UPPER_BOUND = 240; private final Context mContext; private Supplier<TextClassifier> mTextClassifier; + private final ViewConfiguration mViewConfiguration; /** The original TextView text. **/ private String mText; @@ -1088,12 +1091,13 @@ public final class SelectionActionModeHelper { private SelectionResult mLastClassificationResult; /** Whether the TextClassifier has been initialized. */ - private boolean mHot; + private boolean mInitialized; TextClassificationHelper(Context context, Supplier<TextClassifier> textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { init(textClassifier, text, selectionStart, selectionEnd, locales); mContext = Objects.requireNonNull(context); + mViewConfiguration = ViewConfiguration.get(mContext); } @UiThread @@ -1110,13 +1114,13 @@ public final class SelectionActionModeHelper { @WorkerThread public SelectionResult classifyText() { - mHot = true; + mInitialized = true; return performClassification(null /* selection */); } @WorkerThread public SelectionResult suggestSelection() { - mHot = true; + mInitialized = true; trimText(); final TextSelection selection; if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) { @@ -1148,16 +1152,15 @@ public final class SelectionActionModeHelper { /** * Maximum time (in milliseconds) to wait for a textclassifier result before timing out. */ - // TODO: Consider making this a ViewConfiguration. public int getTimeoutDuration() { - if (mHot) { - return 200; + if (mInitialized) { + return mViewConfiguration.getSmartSelectionInitializedTimeout(); } else { // Return a slightly larger number than usual when the TextClassifier is first // initialized. Initialization would usually take longer than subsequent calls to // the TextClassifier. The impact of this on the UI is that we do not show the // selection handles or toolbar until after this timeout. - return 500; + return mViewConfiguration.getSmartSelectionInitializingTimeout(); } } @@ -1205,8 +1208,11 @@ public final class SelectionActionModeHelper { } private void trimText() { - mTrimStart = Math.max(0, mSelectionStart - TRIM_DELTA); - final int referenceEnd = Math.min(mText.length(), mSelectionEnd + TRIM_DELTA); + final int trimDelta = Math.min( + TextClassificationManager.getSettings(mContext).getSmartSelectionTrimDelta(), + TRIM_DELTA_UPPER_BOUND); + mTrimStart = Math.max(0, mSelectionStart - trimDelta); + final int referenceEnd = Math.min(mText.length(), mSelectionEnd + trimDelta); mTrimmedText = mText.subSequence(mTrimStart, referenceEnd); mRelativeStart = mSelectionStart - mTrimStart; mRelativeEnd = mSelectionEnd - mTrimStart; diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 63b9e9befb77..d1c1e40d1578 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -35,6 +35,31 @@ import android.view.WindowManager; @TestApi public final class StartingWindowInfo implements Parcelable { /** + * Prefer nothing or not care the type of starting window. + * @hide + */ + public static final int STARTING_WINDOW_TYPE_NONE = 0; + /** + * Prefer splash screen starting window. + * @hide + */ + public static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 1; + /** + * Prefer snapshot starting window. + * @hide + */ + public static final int STARTING_WINDOW_TYPE_SNAPSHOT = 2; + /** + * @hide + */ + @IntDef(flag = true, prefix = "STARTING_WINDOW_TYPE_", value = { + STARTING_WINDOW_TYPE_NONE, + STARTING_WINDOW_TYPE_SPLASH_SCREEN, + STARTING_WINDOW_TYPE_SNAPSHOT + }) + public @interface StartingWindowType {} + + /** * The {@link TaskInfo} from this task. * @hide */ diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index dc07e44d4d98..f1e5fb95ea54 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -46,7 +46,7 @@ public class TaskSnapshot implements Parcelable { private final int mOrientation; /** See {@link android.view.Surface.Rotation} */ @Surface.Rotation - private int mRotation; + private final int mRotation; /** The size of the snapshot before scaling */ private final Point mTaskSize; private final Rect mContentInsets; @@ -90,15 +90,15 @@ public class TaskSnapshot implements Parcelable { private TaskSnapshot(Parcel source) { mId = source.readLong(); mTopActivityComponent = ComponentName.readFromParcel(source); - mSnapshot = source.readParcelable(null /* classLoader */); + mSnapshot = source.readTypedObject(HardwareBuffer.CREATOR); int colorSpaceId = source.readInt(); mColorSpace = colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length ? ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]) : ColorSpace.get(ColorSpace.Named.SRGB); mOrientation = source.readInt(); mRotation = source.readInt(); - mTaskSize = source.readParcelable(null /* classLoader */); - mContentInsets = source.readParcelable(null /* classLoader */); + mTaskSize = source.readTypedObject(Point.CREATOR); + mContentInsets = source.readTypedObject(Rect.CREATOR); mIsLowResolution = source.readBoolean(); mIsRealSnapshot = source.readBoolean(); mWindowingMode = source.readInt(); @@ -235,13 +235,12 @@ public class TaskSnapshot implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mId); ComponentName.writeToParcel(mTopActivityComponent, dest); - dest.writeParcelable(mSnapshot != null && !mSnapshot.isClosed() ? mSnapshot : null, - 0); + dest.writeTypedObject(mSnapshot != null && !mSnapshot.isClosed() ? mSnapshot : null, 0); dest.writeInt(mColorSpace.getId()); dest.writeInt(mOrientation); dest.writeInt(mRotation); - dest.writeParcelable(mTaskSize, 0); - dest.writeParcelable(mContentInsets, 0); + dest.writeTypedObject(mTaskSize, 0); + dest.writeTypedObject(mContentInsets, 0); dest.writeBoolean(mIsLowResolution); dest.writeBoolean(mIsRealSnapshot); dest.writeInt(mWindowingMode); diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index 4421f06460a0..141f47b130d1 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -139,8 +139,8 @@ public final class TransitionFilter implements Parcelable { boolean matches(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getParent() != null) { - // Only look at the top animating windows. + if (!TransitionInfo.isIndependent(change, info)) { + // Only look at independent animating windows. continue; } if (mActivityType != ACTIVITY_TYPE_UNDEFINED) { diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index d1d49b6d4722..499ce25f8bb9 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -246,6 +246,33 @@ public final class TransitionInfo implements Parcelable { return sb.toString(); } + /** + * Indication that `change` is independent of parents (ie. it has a different type of + * transition vs. "going along for the ride") + */ + public static boolean isIndependent(TransitionInfo.Change change, TransitionInfo info) { + // If the change has no parent (it is root), then it is independent + if (change.getParent() == null) return true; + + // non-visibility changes will just be folded into the parent change, so they aren't + // independent either. + if (change.getMode() == TRANSIT_CHANGE) return false; + + TransitionInfo.Change parentChg = info.getChange(change.getParent()); + while (parentChg != null) { + // If the parent is a visibility change, it will include the results of all child + // changes into itself, so none of its children can be independent. + if (parentChg.getMode() != TRANSIT_CHANGE) return false; + + // If there are no more parents left, then all the parents, so far, have not been + // visibility changes which means this change is indpendent. + if (parentChg.getParent() == null) return true; + + parentChg = info.getChange(parentChg.getParent()); + } + return false; + } + /** Represents the change a WindowContainer undergoes during a transition */ public static final class Change implements Parcelable { private final WindowContainerToken mContainer; diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 3a9f3b9c1128..eecd0cfe66a4 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -74,11 +74,11 @@ interface IAppOpsService { @UnsupportedAppUsage List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops); void getHistoricalOps(int uid, String packageName, String attributionTag, in List<String> ops, - int filter, long beginTimeMillis, long endTimeMillis, int flags, + int historyFlags, int filter, long beginTimeMillis, long endTimeMillis, int flags, in RemoteCallback callback); void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, - in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags, - in RemoteCallback callback); + in List<String> ops, int historyFlags, int filter, long beginTimeMillis, + long endTimeMillis, int flags, in RemoteCallback callback); void offsetHistory(long duration); void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep); void addHistoricalOps(in AppOpsManager.HistoricalOps ops); diff --git a/core/java/com/android/internal/graphics/palette/WuQuantizer.java b/core/java/com/android/internal/graphics/palette/WuQuantizer.java index 01e45f613986..66206bf8297a 100644 --- a/core/java/com/android/internal/graphics/palette/WuQuantizer.java +++ b/core/java/com/android/internal/graphics/palette/WuQuantizer.java @@ -16,7 +16,6 @@ package com.android.internal.graphics.palette; - import java.util.ArrayList; import java.util.List; @@ -120,7 +119,11 @@ public class WuQuantizer implements Quantizer { } } - for (k = 0; k < mMaxColors; ++k) { + // If extraction is run on a set of pixels whose count is less than the + // number of max colors, then colors.length < max colors, and accesses + // to colors[index] inside the for loop throw an ArrayOutOfBoundsException. + int numColorsToCreate = (int) Math.min(mMaxColors, colors.length); + for (k = 0; k < numColorsToCreate; ++k) { weight = getVolume(cube[k], mWt); if (weight > 0) { red = (int) (getVolume(cube[k], mMr) / weight); diff --git a/core/java/com/android/internal/infra/GlobalWhitelistState.java b/core/java/com/android/internal/infra/GlobalWhitelistState.java index 3c081e27305c..7529536de66b 100644 --- a/core/java/com/android/internal/infra/GlobalWhitelistState.java +++ b/core/java/com/android/internal/infra/GlobalWhitelistState.java @@ -99,6 +99,18 @@ public class GlobalWhitelistState { } /** + * Gets packages that are either entirely allowlisted or have components that are allowlisted + * for the given user. + */ + public ArraySet<String> getWhitelistedPackages(@UserIdInt int userId) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return null; + final WhitelistHelper helper = mWhitelisterHelpers.get(userId); + return helper == null ? null : helper.getWhitelistedPackages(); + } + } + + /** * Resets the allowlist for the given user. */ public void resetWhitelist(@NonNull int userId) { diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java index 1d76090f59f3..3e93106822a2 100644 --- a/core/java/com/android/internal/infra/WhitelistHelper.java +++ b/core/java/com/android/internal/infra/WhitelistHelper.java @@ -140,6 +140,15 @@ public final class WhitelistHelper { return mWhitelistedPackages == null ? null : mWhitelistedPackages.get(packageName); } + /** + * Returns a set of all packages that are either entirely allowlisted or have components that + * are allowlisted. + */ + @Nullable + public ArraySet<String> getWhitelistedPackages() { + return mWhitelistedPackages == null ? null : new ArraySet<>(mWhitelistedPackages.keySet()); + } + @Override public String toString() { return "WhitelistHelper[" + mWhitelistedPackages + ']'; diff --git a/core/java/com/android/internal/inputmethod/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java index 21131738cede..3958b9eefefb 100644 --- a/core/java/com/android/internal/inputmethod/CallbackUtils.java +++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java @@ -225,4 +225,31 @@ public final class CallbackUtils { callback.onResult(); } catch (RemoteException ignored) { } } + + /** + * A utility method using given {@link IIInputContentUriTokenResultCallback} to callback the + * result. + * + * @param callback {@link IIInputContentUriTokenResultCallback} to be called back. + * @param resultSupplier the supplier from which the result is provided. + */ + public static void onResult(@NonNull IIInputContentUriTokenResultCallback callback, + @NonNull Supplier<IInputContentUriToken> resultSupplier) { + IInputContentUriToken result = null; + Throwable exception = null; + + try { + result = resultSupplier.get(); + } catch (Throwable throwable) { + exception = throwable; + } + + try { + if (exception != null) { + callback.onError(ThrowableHolder.of(exception)); + return; + } + callback.onResult(result); + } catch (RemoteException ignored) { } + } } diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java index d6a466316ed4..ba3a34334e86 100644 --- a/core/java/com/android/internal/inputmethod/Completable.java +++ b/core/java/com/android/internal/inputmethod/Completable.java @@ -444,6 +444,13 @@ public final class Completable { } /** + * @return an instance of {@link Completable.IInputContentUriToken}. + */ + public static Completable.IInputContentUriToken createIInputContentUriToken() { + return new Completable.IInputContentUriToken(); + } + + /** * @return an instance of {@link Completable.Void}. */ public static Completable.Void createVoid() { @@ -497,6 +504,12 @@ public final class Completable { extends Values<List<android.view.inputmethod.InputMethodInfo>> { } /** + * Completable object of {@link IInputContentUriToken>}. + */ + public static final class IInputContentUriToken + extends Values<com.android.internal.inputmethod.IInputContentUriToken> { } + + /** * Await the result by the {@link Completable.Values}. * * @return the result once {@link ValueBase#onComplete()}. diff --git a/core/java/com/android/internal/inputmethod/IIInputContentUriTokenResultCallback.aidl b/core/java/com/android/internal/inputmethod/IIInputContentUriTokenResultCallback.aidl new file mode 100644 index 000000000000..2e6d2247b51b --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IIInputContentUriTokenResultCallback.aidl @@ -0,0 +1,25 @@ +/* + * 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.inputmethod; + +import com.android.internal.inputmethod.IInputContentUriToken; +import com.android.internal.inputmethod.ThrowableHolder; + +oneway interface IIInputContentUriTokenResultCallback { + void onResult(in IInputContentUriToken result); + void onError(in ThrowableHolder exception); +}
\ No newline at end of file diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index f0e26cf4bbcf..e4dd7b0629b5 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -19,25 +19,31 @@ package com.android.internal.inputmethod; import android.net.Uri; import android.view.inputmethod.InputMethodSubtype; +import com.android.internal.inputmethod.IBooleanResultCallback; import com.android.internal.inputmethod.IInputContentUriToken; +import com.android.internal.inputmethod.IIInputContentUriTokenResultCallback; +import com.android.internal.inputmethod.IVoidResultCallback; /** * Defines priviledged operations that only the current IME is allowed to call. * Actual operations are implemented and handled by InputMethodManagerService. */ -interface IInputMethodPrivilegedOperations { - void setImeWindowStatus(int vis, int backDisposition); - void reportStartInput(in IBinder startInputToken); - IInputContentUriToken createInputContentUriToken(in Uri contentUri, in String packageName); - void reportFullscreenMode(boolean fullscreen); - void setInputMethod(String id); - void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype); - void hideMySoftInput(int flags); - void showMySoftInput(int flags); - void updateStatusIcon(String packageName, int iconId); - boolean switchToPreviousInputMethod(); - boolean switchToNextInputMethod(boolean onlyCurrentIme); - boolean shouldOfferSwitchingToNextInputMethod(); - void notifyUserAction(); - void applyImeVisibility(IBinder showOrHideInputToken, boolean setVisible); +oneway interface IInputMethodPrivilegedOperations { + void setImeWindowStatus(int vis, int backDisposition, in IVoidResultCallback resultCallback); + void reportStartInput(in IBinder startInputToken, in IVoidResultCallback resultCallback); + void createInputContentUriToken(in Uri contentUri, in String packageName, + in IIInputContentUriTokenResultCallback resultCallback); + void reportFullscreenMode(boolean fullscreen, in IVoidResultCallback resultCallback); + void setInputMethod(String id, in IVoidResultCallback resultCallback); + void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype, + in IVoidResultCallback resultCallback); + void hideMySoftInput(int flags, in IVoidResultCallback resultCallback); + void showMySoftInput(int flags, in IVoidResultCallback resultCallback); + void updateStatusIcon(String packageName, int iconId, in IVoidResultCallback resultCallback); + void switchToPreviousInputMethod(in IBooleanResultCallback resultCallback); + void switchToNextInputMethod(boolean onlyCurrentIme, in IBooleanResultCallback resultCallback); + void shouldOfferSwitchingToNextInputMethod(in IBooleanResultCallback resultCallback); + void notifyUserAction(in IVoidResultCallback resultCallback); + void applyImeVisibility(IBinder showOrHideInputToken, boolean setVisible, + in IVoidResultCallback resultCallback); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index c353de88dc3b..93374ba0cf46 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -228,6 +228,8 @@ public final class InputMethodDebug { return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR"; case SoftInputShowHideReason.HIDE_REMOVE_CLIENT: return "HIDE_REMOVE_CLIENT"; + case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY: + return "SHOW_RESTORE_IME_VISIBILITY"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index d6730e8b0bdb..04cf3f3e546f 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -95,7 +95,8 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatus(int, int)}. + * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatus(int, int, + * IVoidResultCallback)}. * * @param vis visibility flags * @param backDisposition disposition flags @@ -112,14 +113,17 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.setImeWindowStatus(vis, backDisposition); + final Completable.Void value = Completable.createVoid(); + ops.setImeWindowStatus(vis, backDisposition, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#reportStartInput(IBinder)}. + * Calls {@link IInputMethodPrivilegedOperations#reportStartInput(IBinder, + * IVoidResultCallback)}. * * @param startInputToken {@link IBinder} token to distinguish startInput session */ @@ -130,14 +134,17 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.reportStartInput(startInputToken); + final Completable.Void value = Completable.createVoid(); + ops.reportStartInput(startInputToken, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#createInputContentUriToken(Uri, String)}. + * Calls {@link IInputMethodPrivilegedOperations#createInputContentUriToken(Uri, String, + * IIInputContentUriTokenResultCallback)}. * * @param contentUri Content URI to which a temporary read permission should be granted * @param packageName Indicates what package needs to have a temporary read permission @@ -151,7 +158,10 @@ public final class InputMethodPrivilegedOperations { return null; } try { - return ops.createInputContentUriToken(contentUri, packageName); + final Completable.IInputContentUriToken value = + Completable.createIInputContentUriToken(); + ops.createInputContentUriToken(contentUri, packageName, ResultCallbacks.of(value)); + return Completable.getResult(value); } catch (RemoteException e) { // For historical reasons, this error was silently ignored. // Note that the caller already logs error so we do not need additional Log.e() here. @@ -161,7 +171,8 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#reportFullscreenMode(boolean)}. + * Calls {@link IInputMethodPrivilegedOperations#reportFullscreenMode(boolean, + * IVoidResultCallback)}. * * @param fullscreen {@code true} if the IME enters full screen mode */ @@ -172,14 +183,17 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.reportFullscreenMode(fullscreen); + final Completable.Void value = Completable.createVoid(); + ops.reportFullscreenMode(fullscreen, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#updateStatusIcon(String, int)}. + * Calls {@link IInputMethodPrivilegedOperations#updateStatusIcon(String, int, + * IVoidResultCallback)}. * * @param packageName package name from which the status icon should be loaded * @param iconResId resource ID of the icon to be loaded @@ -191,14 +205,16 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.updateStatusIcon(packageName, iconResId); + final Completable.Void value = Completable.createVoid(); + ops.updateStatusIcon(packageName, iconResId, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#setInputMethod(String)}. + * Calls {@link IInputMethodPrivilegedOperations#setInputMethod(String, IVoidResultCallback)}. * * @param id IME ID of the IME to switch to * @see android.view.inputmethod.InputMethodInfo#getId() @@ -210,7 +226,9 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.setInputMethod(id); + final Completable.Void value = Completable.createVoid(); + ops.setInputMethod(id, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -218,7 +236,7 @@ public final class InputMethodPrivilegedOperations { /** * Calls {@link IInputMethodPrivilegedOperations#setInputMethodAndSubtype(String, - * InputMethodSubtype)} + * InputMethodSubtype, IVoidResultCallback)} * * @param id IME ID of the IME to switch to * @param subtype {@link InputMethodSubtype} to switch to @@ -231,14 +249,16 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.setInputMethodAndSubtype(id, subtype); + final Completable.Void value = Completable.createVoid(); + ops.setInputMethodAndSubtype(id, subtype, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int)} + * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)} * * @param flags additional operating flags * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY @@ -251,14 +271,16 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.hideMySoftInput(flags); + final Completable.Void value = Completable.createVoid(); + ops.hideMySoftInput(flags, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int)} + * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, IVoidResultCallback)} * * @param flags additional operating flags * @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT @@ -271,14 +293,17 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.showMySoftInput(flags); + final Completable.Void value = Completable.createVoid(); + ops.showMySoftInput(flags, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#switchToPreviousInputMethod()} + * Calls {@link IInputMethodPrivilegedOperations#switchToPreviousInputMethod( + * IBooleanResultCallback)} * * @return {@code true} if handled */ @@ -289,14 +314,17 @@ public final class InputMethodPrivilegedOperations { return false; } try { - return ops.switchToPreviousInputMethod(); + final Completable.Boolean value = Completable.createBoolean(); + ops.switchToPreviousInputMethod(ResultCallbacks.of(value)); + return Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean)} + * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean, + * IBooleanResultCallback)} * * @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same * IME @@ -309,14 +337,17 @@ public final class InputMethodPrivilegedOperations { return false; } try { - return ops.switchToNextInputMethod(onlyCurrentIme); + final Completable.Boolean value = Completable.createBoolean(); + ops.switchToNextInputMethod(onlyCurrentIme, ResultCallbacks.of(value)); + return Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#shouldOfferSwitchingToNextInputMethod()} + * Calls {@link IInputMethodPrivilegedOperations#shouldOfferSwitchingToNextInputMethod( + * IBooleanResultCallback)} * * @return {@code true} if the IEM should offer a way to globally switch IME */ @@ -327,14 +358,16 @@ public final class InputMethodPrivilegedOperations { return false; } try { - return ops.shouldOfferSwitchingToNextInputMethod(); + final Completable.Boolean value = Completable.createBoolean(); + ops.shouldOfferSwitchingToNextInputMethod(ResultCallbacks.of(value)); + return Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#notifyUserAction()} + * Calls {@link IInputMethodPrivilegedOperations#notifyUserAction(IVoidResultCallback)} */ @AnyThread public void notifyUserAction() { @@ -343,14 +376,17 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.notifyUserAction(); + final Completable.Void value = Completable.createVoid(); + ops.notifyUserAction(ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(IBinder, boolean)}. + * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(IBinder, boolean, + * IVoidResultCallback)}. * * @param showOrHideInputToken placeholder token that maps to window requesting * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or @@ -365,7 +401,9 @@ public final class InputMethodPrivilegedOperations { return; } try { - ops.applyImeVisibility(showOrHideInputToken, setVisible); + final Completable.Void value = Completable.createVoid(); + ops.applyImeVisibility(showOrHideInputToken, setVisible, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java index 2a48c1f60aa9..c56ed2d19927 100644 --- a/core/java/com/android/internal/inputmethod/ResultCallbacks.java +++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java @@ -387,4 +387,41 @@ public final class ResultCallbacks { } }; } + + /** + * Creates {@link IIInputContentUriTokenResultCallback.Stub} that is to set + * {@link Completable.IInputContentUriToken} when receiving the result. + * + * @param value {@link Completable.IInputContentUriToken} to be set when receiving the result. + * @return {@link IIInputContentUriTokenResultCallback.Stub} that can be passed as a binder IPC + * parameter. + */ + @AnyThread + public static IIInputContentUriTokenResultCallback.Stub of( + @NonNull Completable.IInputContentUriToken value) { + final AtomicReference<WeakReference<Completable.IInputContentUriToken>> + atomicRef = new AtomicReference<>(new WeakReference<>(value)); + + return new IIInputContentUriTokenResultCallback.Stub() { + @BinderThread + @Override + public void onResult(IInputContentUriToken result) { + final Completable.IInputContentUriToken value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onComplete(result); + } + + @BinderThread + @Override + public void onError(ThrowableHolder throwableHolder) { + final Completable.IInputContentUriToken value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onError(throwableHolder); + } + }; + } } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 1553e2eb0793..f1cdf2b38c4c 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -49,7 +49,8 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_RECENTS_ANIMATION, SoftInputShowHideReason.HIDE_BUBBLES, SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR, - SoftInputShowHideReason.HIDE_REMOVE_CLIENT}) + SoftInputShowHideReason.HIDE_REMOVE_CLIENT, + SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY}) public @interface SoftInputShowHideReason { /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = 0; @@ -167,4 +168,10 @@ public @interface SoftInputShowHideReason { * Hide soft input when a {@link com.android.internal.view.IInputMethodClient} is removed. */ int HIDE_REMOVE_CLIENT = 21; + + /** + * Show soft input when the system invoking + * {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}. + */ + int SHOW_RESTORE_IME_VISIBILITY = 22; } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 342456a58091..db0b48e130a3 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -203,6 +203,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); mCancelled = true; removeObservers(); + if (mListener != null) { + mListener.onNotifyCujEvents(mSession, InteractionJankMonitor.ACTION_SESSION_CANCEL); + } } @Override @@ -324,11 +327,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } if (info.surfaceControlCallbackFired) { totalFramesCount++; - - // Only count missed frames if it's not stuffed. if ((info.jankType & PREDICTION_ERROR) != 0 - || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0 - && (info.jankType & BUFFER_STUFFING) == 0)) { + || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0)) { Log.w(TAG, "Missed App frame:" + info.jankType); missedAppFramesCount++; } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 0294ec398484..fbc92c1f99c4 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -99,7 +99,7 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN"; - public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; + public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL"; public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED"; public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME"; public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP"; diff --git a/core/java/com/android/internal/net/NetworkUtilsInternal.java b/core/java/com/android/internal/net/NetworkUtilsInternal.java index 571d7e721094..052959abff69 100644 --- a/core/java/com/android/internal/net/NetworkUtilsInternal.java +++ b/core/java/com/android/internal/net/NetworkUtilsInternal.java @@ -22,6 +22,8 @@ import static android.system.OsConstants.AF_INET6; import android.annotation.NonNull; import android.system.Os; +import java.io.FileDescriptor; + /** @hide */ public class NetworkUtilsInternal { @@ -36,6 +38,20 @@ public class NetworkUtilsInternal { public static native void setAllowNetworkingForProcess(boolean allowNetworking); /** + * Protect {@code fd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public static native boolean protectFromVpn(FileDescriptor fd); + + /** + * Protect {@code socketfd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public static native boolean protectFromVpn(int socketfd); + + /** * Returns true if the hostname is weakly validated. * @param hostname Name of host to validate. * @return True if it's a valid-ish hostname. diff --git a/core/java/com/android/internal/os/DischargedPowerCalculator.java b/core/java/com/android/internal/os/BatteryChargeCalculator.java index e94020cc4f18..dc72f3267390 100644 --- a/core/java/com/android/internal/os/DischargedPowerCalculator.java +++ b/core/java/com/android/internal/os/BatteryChargeCalculator.java @@ -27,10 +27,10 @@ import java.util.List; /** * Estimates the battery discharge amounts. */ -public class DischargedPowerCalculator extends PowerCalculator { +public class BatteryChargeCalculator extends PowerCalculator { private final double mBatteryCapacity; - public DischargedPowerCalculator(PowerProfile powerProfile) { + public BatteryChargeCalculator(PowerProfile powerProfile) { mBatteryCapacity = powerProfile.getBatteryCapacity(); } @@ -42,6 +42,16 @@ public class DischargedPowerCalculator extends PowerCalculator { .setDischargedPowerRange( batteryStats.getLowDischargeAmountSinceCharge() * mBatteryCapacity / 100, batteryStats.getHighDischargeAmountSinceCharge() * mBatteryCapacity / 100); + + final long batteryTimeRemainingMs = batteryStats.computeBatteryTimeRemaining(rawRealtimeUs); + if (batteryTimeRemainingMs != -1) { + builder.setBatteryTimeRemainingMs(batteryTimeRemainingMs / 1000); + } + + final long chargeTimeRemainingMs = batteryStats.computeChargeTimeRemaining(rawRealtimeUs); + if (chargeTimeRemainingMs != -1) { + builder.setChargeTimeRemainingMs(chargeTimeRemainingMs / 1000); + } } @Override diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 15b584d1fd06..619cd8e2fc27 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -55,7 +55,7 @@ public class BatteryUsageStatsProvider { mPowerCalculators = new ArrayList<>(); // Power calculators are applied in the order of registration - mPowerCalculators.add(new DischargedPowerCalculator(mPowerProfile)); + mPowerCalculators.add(new BatteryChargeCalculator(mPowerProfile)); mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile)); mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index 0aa54f556b92..3f01ebb1afe7 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -6,6 +6,7 @@ per-file *Cpu* = file:CPU_OWNERS per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS per-file BatteryStats* = file:/BATTERY_STATS_OWNERS per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS +per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 6b1d408bee9a..8b5a62ada1c5 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -202,7 +202,7 @@ public final class Zygote { public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map"; /** List of allowlisted packages and its app data info: volume uuid and inode. */ - public static final String WHITELISTED_DATA_INFO_MAP = "--whitelisted-data-info-map"; + public static final String ALLOWLISTED_DATA_INFO_MAP = "--allowlisted-data-info-map"; /** Bind mount app storage dirs to lower fs not via fuse */ public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs"; @@ -324,7 +324,7 @@ public final class Zygote { * @param isTopApp true if the process is for top (high priority) application. * @param pkgDataInfoList A list that stores related packages and its app data * info: volume uuid and inode. - * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. + * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. * @@ -334,14 +334,14 @@ public final class Zygote { static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList, + boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) { ZygoteHooks.preFork(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, - pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs, + pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs, bindMountAppStorageDirs); if (pid == 0) { // Note that this event ends at the end of handleChildProc, @@ -364,7 +364,7 @@ public final class Zygote { int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, String[] pkgDataInfoList, - String[] whitelistedDataInfoList, boolean bindMountAppDataDirs, + String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs); /** @@ -392,18 +392,18 @@ public final class Zygote { * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name, * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid, * app_b_ce_inode, ...]; - * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. + * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps. * @param bindMountAppDataDirs True if the zygote needs to mount data dirs. * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs. */ private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, - String[] pkgDataInfoList, String[] whitelistedDataInfoList, + String[] pkgDataInfoList, String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) { nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, startChildZygote, instructionSet, appDataDir, isTopApp, - pkgDataInfoList, whitelistedDataInfoList, + pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs, bindMountAppStorageDirs); // Note that this event ends at the end of handleChildProc. @@ -428,7 +428,7 @@ public final class Zygote { private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, - String[] pkgDataInfoList, String[] whitelistedDataInfoList, + String[] pkgDataInfoList, String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs); /** @@ -807,7 +807,7 @@ public final class Zygote { args.mRuntimeFlags, rlimits, args.mMountExternal, args.mSeInfo, args.mNiceName, args.mStartChildZygote, args.mInstructionSet, args.mAppDataDir, args.mIsTopApp, - args.mPkgDataInfoList, args.mWhitelistedDataInfoList, + args.mPkgDataInfoList, args.mAllowlistedDataInfoList, args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index 65b454d47db2..ef8398294c5b 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -230,7 +230,7 @@ class ZygoteArguments { * A list that stores all allowlisted app data info: volume uuid and inode. * Null if it does need to do app data isolation. */ - String[] mWhitelistedDataInfoList; + String[] mAllowlistedDataInfoList; /** * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS @@ -475,8 +475,8 @@ class ZygoteArguments { } } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) { mPkgDataInfoList = getAssignmentList(arg); - } else if (arg.startsWith(Zygote.WHITELISTED_DATA_INFO_MAP)) { - mWhitelistedDataInfoList = getAssignmentList(arg); + } else if (arg.startsWith(Zygote.ALLOWLISTED_DATA_INFO_MAP)) { + mAllowlistedDataInfoList = getAssignmentList(arg); } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) { mBindMountAppStorageDirs = true; } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) { diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 37c75907061c..1673362028f9 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -265,7 +265,7 @@ class ZygoteConnection { fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList, - parsedArgs.mWhitelistedDataInfoList, parsedArgs.mBindMountAppDataDirs, + parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs); try { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 9840013935f8..9a91d2028953 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -124,6 +124,7 @@ import com.android.internal.widget.DecorCaptionView; import com.android.internal.widget.FloatingToolbar; import java.util.List; +import java.util.function.Consumer; /** @hide */ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks { @@ -282,14 +283,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private final Paint mLegacyNavigationBarBackgroundPaint = new Paint(); private Insets mBackgroundInsets = Insets.NONE; private Insets mLastBackgroundInsets = Insets.NONE; - private int mLastBackgroundBlurRadius = 0; private boolean mDrawLegacyNavigationBarBackground; private PendingInsetsController mPendingInsetsController = new PendingInsetsController(); + + private int mOriginalBackgroundBlurRadius = 0; + private int mBackgroundBlurRadius = 0; + private int mLastBackgroundBlurRadius = 0; + private boolean mCrossWindowBlurEnabled; private final ViewTreeObserver.OnPreDrawListener mBackgroundBlurOnPreDrawListener = () -> { - updateBackgroundBlur(); + updateBackgroundBlurCorners(); return true; }; + private Consumer<Boolean> mCrossWindowBlurEnabledListener; DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { @@ -1272,23 +1278,17 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } if (mBackgroundInsets.equals(mLastBackgroundInsets) - && mWindow.mBackgroundBlurRadius == mLastBackgroundBlurRadius + && mBackgroundBlurRadius == mLastBackgroundBlurRadius && mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) { return; } Drawable destDrawable = mOriginalBackgroundDrawable; - if (mWindow.mBackgroundBlurRadius > 0 && getViewRootImpl() != null - && mWindow.isTranslucent()) { - if (mBackgroundBlurDrawable == null) { - mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable(); - } + if (mBackgroundBlurRadius > 0) { destDrawable = new LayerDrawable(new Drawable[] {mBackgroundBlurDrawable, mOriginalBackgroundDrawable}); - mLastBackgroundBlurRadius = mWindow.mBackgroundBlurRadius; } - if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) { destDrawable = new InsetDrawable(destDrawable, mBackgroundInsets.left, mBackgroundInsets.top, @@ -1309,23 +1309,60 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind super.setBackgroundDrawable(destDrawable); mLastBackgroundInsets = mBackgroundInsets; + mLastBackgroundBlurRadius = mBackgroundBlurRadius; mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable; } - private void updateBackgroundBlur() { + private void updateBackgroundBlurCorners() { if (mBackgroundBlurDrawable == null) return; + float cornerRadius = 0; // If the blur radius is 0, the blur region won't be sent to surface flinger, so we don't // need to calculate the corner radius. - if (mWindow.mBackgroundBlurRadius > 0) { - if (mOriginalBackgroundDrawable != null) { - final Outline outline = new Outline(); - mOriginalBackgroundDrawable.getOutline(outline); - mBackgroundBlurDrawable.setCornerRadius(outline.mMode == Outline.MODE_ROUND_RECT - ? outline.getRadius() : 0); + if (mBackgroundBlurRadius != 0 && mOriginalBackgroundDrawable != null) { + final Outline outline = new Outline(); + mOriginalBackgroundDrawable.getOutline(outline); + cornerRadius = outline.mMode == Outline.MODE_ROUND_RECT ? outline.getRadius() : 0; + } + mBackgroundBlurDrawable.setCornerRadius(cornerRadius); + } + + private void updateBackgroundBlurRadius() { + if (getViewRootImpl() == null) return; + + mBackgroundBlurRadius = mCrossWindowBlurEnabled && mWindow.isTranslucent() + ? mOriginalBackgroundBlurRadius : 0; + if (mBackgroundBlurDrawable == null && mBackgroundBlurRadius > 0) { + mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable(); + } + + if (mBackgroundBlurDrawable != null) { + mBackgroundBlurDrawable.setBlurRadius(mBackgroundBlurRadius); + updateBackgroundDrawable(); + } + } + + void setBackgroundBlurRadius(int blurRadius) { + mOriginalBackgroundBlurRadius = blurRadius; + if (blurRadius > 0) { + if (mCrossWindowBlurEnabledListener == null) { + mCrossWindowBlurEnabledListener = enabled -> { + mCrossWindowBlurEnabled = enabled; + updateBackgroundBlurRadius(); + }; + getContext().getSystemService(WindowManager.class) + .addCrossWindowBlurEnabledListener(mCrossWindowBlurEnabledListener); + getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener); + } else { + updateBackgroundBlurRadius(); } + } else if (mCrossWindowBlurEnabledListener != null) { + mCrossWindowBlurEnabledListener = null; + getContext().getSystemService(WindowManager.class) + .removeCrossWindowBlurEnabledListener(mCrossWindowBlurEnabledListener); + getViewTreeObserver().removeOnPreDrawListener(mBackgroundBlurOnPreDrawListener); + updateBackgroundBlurRadius(); } - mBackgroundBlurDrawable.setBlurRadius(mWindow.mBackgroundBlurRadius); } @Override @@ -1758,9 +1795,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind cb.onAttachedToWindow(); } - getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener); - updateBackgroundDrawable(); - if (mFeatureId == -1) { /* * The main window has been attached, try to restore any panels @@ -1782,6 +1816,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // renderer about it. mBackdropFrameRenderer.onConfigurationChange(); } + + updateBackgroundBlurRadius(); + mWindow.onViewRootImplSet(getViewRootImpl()); } @@ -1794,8 +1831,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind cb.onDetachedFromWindow(); } - getViewTreeObserver().removeOnPreDrawListener(mBackgroundBlurOnPreDrawListener); - if (mWindow.mDecorContentParent != null) { mWindow.mDecorContentParent.dismissPopups(); } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index d06413c3ca15..6049486b380c 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -75,6 +75,7 @@ import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; import android.view.ContextThemeWrapper; +import android.view.CrossWindowBlurListeners; import android.view.Gravity; import android.view.IRotationWatcher.Stub; import android.view.IScrollCaptureCallbacks; @@ -258,7 +259,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { Drawable mBackgroundDrawable = null; Drawable mBackgroundFallbackDrawable = null; - int mBackgroundBlurRadius = 0; + private int mBackgroundBlurRadius = 0; private boolean mLoadElevation = true; private float mElevation; @@ -1527,9 +1528,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public final void setBackgroundBlurRadius(int blurRadius) { super.setBackgroundBlurRadius(blurRadius); - if (getContext().getPackageManager().hasSystemFeature( - PackageManager.FEATURE_CROSS_LAYER_BLUR)) { - mBackgroundBlurRadius = Math.max(blurRadius, 0); + if (CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED) { + if (mBackgroundBlurRadius != Math.max(blurRadius, 0)) { + mBackgroundBlurRadius = Math.max(blurRadius, 0); + mDecor.setBackgroundBlurRadius(mBackgroundBlurRadius); + } } } @@ -2556,8 +2559,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { params.flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND; } - params.blurBehindRadius = a.getDimensionPixelSize( - android.R.styleable.Window_windowBlurBehindRadius, 0); + params.setBlurBehindRadius(a.getDimensionPixelSize( + android.R.styleable.Window_windowBlurBehindRadius, 0)); } setBackgroundBlurRadius(a.getDimensionPixelSize( diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 1b1e0bfb3a58..8ecc80946141 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -32,7 +32,6 @@ import android.app.Person; import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.Color; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.GradientDrawable; @@ -62,11 +61,9 @@ import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.graphics.ColorUtils; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.function.Consumer; @@ -118,7 +115,6 @@ public class ConversationLayout extends FrameLayout private ViewGroup mExpandButtonAndContentContainer; private NotificationExpandButton mExpandButton; private MessagingLinearLayout mImageMessageContainer; - private int mExpandButtonExpandedTopMargin; private int mBadgedSideMargins; private int mConversationAvatarSize; private int mConversationAvatarSizeExpanded; @@ -147,7 +143,6 @@ public class ConversationLayout extends FrameLayout private int mFacePileProtectionWidth; private int mFacePileProtectionWidthExpanded; private boolean mImportantConversation; - private TextView mUnreadBadge; private View mFeedbackIcon; private float mMinTouchSize; private Icon mConversationIcon; @@ -245,8 +240,6 @@ public class ConversationLayout extends FrameLayout mContentContainer = findViewById(R.id.notification_action_list_margin_target); mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container); mExpandButton = findViewById(R.id.expand_button); - mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize( - R.dimen.conversation_expand_button_top_margin_expanded); mNotificationHeaderExpandedPadding = getResources().getDimensionPixelSize( R.dimen.conversation_header_expanded_padding_end); mContentMarginEnd = getResources().getDimensionPixelSize( @@ -286,7 +279,6 @@ public class ConversationLayout extends FrameLayout mAppName.setOnVisibilityChangedListener((visibility) -> { onAppNameVisibilityChanged(); }); - mUnreadBadge = findViewById(R.id.conversation_unread_count); mConversationContentStart = getResources().getDimensionPixelSize( R.dimen.conversation_content_start); mInternalButtonPadding @@ -426,17 +418,7 @@ public class ConversationLayout extends FrameLayout /** @hide */ public void setUnreadCount(int unreadCount) { - boolean visible = mIsCollapsed && unreadCount > 1; - mUnreadBadge.setVisibility(visible ? VISIBLE : GONE); - if (visible) { - CharSequence text = unreadCount >= 100 - ? getResources().getString(R.string.unread_convo_overflow, 99) - : String.format(Locale.getDefault(), "%d", unreadCount); - mUnreadBadge.setText(text); - mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); - boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; - mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); - } + mExpandButton.setNumber(unreadCount); } private void addRemoteInputHistoryToMessages( @@ -1132,15 +1114,16 @@ public class ConversationLayout extends FrameLayout } private void updateExpandButton() { - int gravity; - int topMargin = 0; + int buttonGravity; + int containerHeight; ViewGroup newContainer; if (mIsCollapsed) { - gravity = Gravity.CENTER; + buttonGravity = Gravity.CENTER; + containerHeight = ViewGroup.LayoutParams.WRAP_CONTENT; newContainer = mExpandButtonAndContentContainer; } else { - gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - topMargin = mExpandButtonExpandedTopMargin; + buttonGravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + containerHeight = ViewGroup.LayoutParams.MATCH_PARENT; newContainer = this; } mExpandButton.setExpanded(!mIsCollapsed); @@ -1149,14 +1132,14 @@ public class ConversationLayout extends FrameLayout // content when collapsed, but allows the content to flow under it when expanded. if (newContainer != mExpandButtonContainer.getParent()) { ((ViewGroup) mExpandButtonContainer.getParent()).removeView(mExpandButtonContainer); + mExpandButtonContainer.getLayoutParams().height = containerHeight; newContainer.addView(mExpandButtonContainer); } // update if the expand button is centered LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mExpandButton.getLayoutParams(); - layoutParams.gravity = gravity; - layoutParams.topMargin = topMargin; + layoutParams.gravity = buttonGravity; mExpandButton.setLayoutParams(layoutParams); } @@ -1210,6 +1193,7 @@ public class ConversationLayout extends FrameLayout mExpandButtonContainer.setVisibility(GONE); mConversationIconContainer.setOnClickListener(null); } + mExpandButton.setVisibility(VISIBLE); updateContentEndPaddings(); } diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java index 8add34f328bf..fc4cc5764eaf 100644 --- a/core/java/com/android/internal/widget/NotificationExpandButton.java +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -16,30 +16,42 @@ package com.android.internal.widget; -import static com.android.internal.widget.ColoredIconHelper.applyGrayTint; - +import android.annotation.ColorInt; import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Rect; import android.util.AttributeSet; import android.view.RemotableViewMethod; +import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; +import java.util.Locale; + /** * An expand button in a notification */ @RemoteViews.RemoteView -public class NotificationExpandButton extends ImageView { +public class NotificationExpandButton extends FrameLayout { - private final int mMinTouchTargetSize; + private View mPillView; + private TextView mNumberView; + private ImageView mIconView; private boolean mExpanded; - private int mOriginalNotificationColor; + private int mNumber; + private int mDefaultPillColor; + private int mDefaultTextColor; + private int mHighlightPillColor; + private int mHighlightTextColor; + private boolean mDisallowColor; public NotificationExpandButton(Context context) { this(context, null, 0, 0); @@ -57,7 +69,14 @@ public class NotificationExpandButton extends ImageView { public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mMinTouchTargetSize = (int) (getResources().getDisplayMetrics().density * 48 + 0.5); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mPillView = findViewById(R.id.expand_button_pill); + mNumberView = findViewById(R.id.expand_button_number); + mIconView = findViewById(R.id.expand_button_icon); } /** @@ -72,7 +91,6 @@ public class NotificationExpandButton extends ImageView { } else { super.getBoundsOnScreen(outRect, clipToParent); } - extendRectToMinTouchSize(outRect); } /** @@ -89,32 +107,12 @@ public class NotificationExpandButton extends ImageView { return super.pointInView(localX, localY, slop); } - @RemotableViewMethod - public void setOriginalNotificationColor(int color) { - mOriginalNotificationColor = color; - } - - public int getOriginalNotificationColor() { - return mOriginalNotificationColor; - } - /** - * Set the button's color filter: to gray if true, otherwise colored. - * If this button has no original color, this has no effect. + * Disable the use of the accent colors for this view, if true. */ public void setGrayedOut(boolean shouldApply) { - applyGrayTint(mContext, getDrawable(), shouldApply, mOriginalNotificationColor); - } - - private void extendRectToMinTouchSize(Rect rect) { - if (rect.width() < mMinTouchTargetSize) { - rect.left = rect.centerX() - mMinTouchTargetSize / 2; - rect.right = rect.left + mMinTouchTargetSize; - } - if (rect.height() < mMinTouchTargetSize) { - rect.top = rect.centerY() - mMinTouchTargetSize / 2; - rect.bottom = rect.top + mMinTouchTargetSize; - } + mDisallowColor = shouldApply; + updateColors(); } @Override @@ -129,10 +127,10 @@ public class NotificationExpandButton extends ImageView { @RemotableViewMethod public void setExpanded(boolean expanded) { mExpanded = expanded; - updateExpandButton(); + updateExpandedState(); } - private void updateExpandButton() { + private void updateExpandedState() { int drawableId; int contentDescriptionId; if (mExpanded) { @@ -142,8 +140,89 @@ public class NotificationExpandButton extends ImageView { drawableId = R.drawable.ic_expand_notification; contentDescriptionId = R.string.expand_button_content_description_collapsed; } - setImageDrawable(getContext().getDrawable(drawableId)); - setColorFilter(mOriginalNotificationColor); setContentDescription(mContext.getText(contentDescriptionId)); + mIconView.setImageDrawable(getContext().getDrawable(drawableId)); + + // changing the expanded state can affect the number display + updateNumber(); + } + + private void updateNumber() { + if (shouldShowNumber()) { + CharSequence text = mNumber >= 100 + ? getResources().getString(R.string.unread_convo_overflow, 99) + : String.format(Locale.getDefault(), "%d", mNumber); + mNumberView.setText(text); + mNumberView.setVisibility(VISIBLE); + } else { + mNumberView.setVisibility(GONE); + } + + // changing number can affect the color + updateColors(); + } + + private void updateColors() { + if (shouldShowNumber() && !mDisallowColor) { + mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor)); + mIconView.setColorFilter(mHighlightTextColor); + mNumberView.setTextColor(mHighlightTextColor); + } else { + mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor)); + mIconView.setColorFilter(mDefaultTextColor); + mNumberView.setTextColor(mDefaultTextColor); + } + } + + private boolean shouldShowNumber() { + return !mExpanded && mNumber > 1; + } + + /** + * Set the color used for the expand chevron and the text + */ + @RemotableViewMethod + public void setDefaultTextColor(int color) { + mDefaultTextColor = color; + updateColors(); + } + + /** + * Sets the color used to for the expander when there is no number shown + */ + @RemotableViewMethod + public void setDefaultPillColor(@ColorInt int color) { + mDefaultPillColor = color; + updateColors(); + } + + /** + * Set the color used for the expand chevron and the text + */ + @RemotableViewMethod + public void setHighlightTextColor(int color) { + mHighlightTextColor = color; + updateColors(); + } + + /** + * Sets the color used to highlight the expander when there is a number shown + */ + @RemotableViewMethod + public void setHighlightPillColor(@ColorInt int color) { + mHighlightPillColor = color; + updateColors(); + } + + /** + * Sets the number shown inside the expand button. + * This only appears when the expand button is collapsed, and when greater than 1. + */ + @RemotableViewMethod + public void setNumber(int number) { + if (mNumber != number) { + mNumber = number; + updateNumber(); + } } } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index b40ffb0136f2..bac6bbe43c91 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -95,9 +95,6 @@ public class SystemConfig { // property for runtime configuration differentiation in vendor private static final String VENDOR_SKU_PROPERTY = "ro.boot.product.vendor.sku"; - // property for background blur support in surface flinger - private static final String BLUR_PROPERTY = "ro.surface_flinger.supports_background_blur"; - // Group-ids that are given to all packages as read from etc/permissions/*.xml. int[] mGlobalGids = EmptyArray.INT; @@ -1237,8 +1234,7 @@ public class SystemConfig { final int incrementalVersion = IncrementalManager.getVersion(); if (incrementalVersion > 0) { - addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0); - addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION, incrementalVersion); + addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, incrementalVersion); } if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) { @@ -1249,10 +1245,6 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0); } - if (SystemProperties.get(BLUR_PROPERTY, "default").equals("1")) { - addFeature(PackageManager.FEATURE_CROSS_LAYER_BLUR, 0); - } - if (SensorPrivacyManager.USE_MICROPHONE_TOGGLE) { addFeature(PackageManager.FEATURE_MICROPHONE_TOGGLE, 0); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 2287900795a5..d6d33873adaa 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -51,6 +51,7 @@ cc_library_shared { "android_util_XmlBlock.cpp", "android_util_jar_StrictJarFile.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", + ":deviceproductinfoconstants_aidl", ], include_dirs: [ @@ -150,7 +151,7 @@ cc_library_shared { "android_os_VintfRuntimeInfo.cpp", "android_os_incremental_IncrementalManager.cpp", "android_net_LocalSocketImpl.cpp", - "android_net_NetUtils.cpp", + "android_net_NetworkUtils.cpp", "android_service_DataLoaderService.cpp", "android_util_AssetManager.cpp", "android_util_Binder.cpp", diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 35d1d7bd7946..f6629fd250f6 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -43,6 +43,7 @@ per-file android_os_HwRemoteBinder* = file:platform/system/libhwbinder:/OWNERS per-file EphemeralStorage* = file:platform/system/libhwbinder:/OWNERS per-file *Zygote* = file:/ZYGOTE_OWNERS +per-file fd_utils.* = file:/ZYGOTE_OWNERS per-file Android.bp = file:platform/build/soong:/OWNERS per-file android_animation_* = file:/core/java/android/animation/OWNERS per-file android_app_admin_* = file:/core/java/android/app/admin/OWNERS diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index c7439f1b32d4..b0c575162b56 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -83,6 +83,10 @@ class LoaderAssetsProvider : public AssetsProvider { return true; } + std::optional<std::string_view> GetPath() const override { + return {}; + } + const std::string& GetDebugName() const override { return debug_name_; } @@ -358,8 +362,16 @@ static jlong NativeGetFinalizer(JNIEnv* /*env*/, jclass /*clazz*/) { } static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) { - const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); - return env->NewStringUTF(apk_assets->GetPath().c_str()); + auto apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + if (auto path = apk_assets->GetPath()) { + return env->NewStringUTF(path->data()); + } + return nullptr; +} + +static jstring NativeGetDebugName(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + auto apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + return env->NewStringUTF(apk_assets->GetDebugName().c_str()); } static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { @@ -467,6 +479,7 @@ static const JNINativeMethod gApkAssetsMethods[] = { (void*)NativeLoadFromFdOffset}, {"nativeGetFinalizer", "()J", (void*)NativeGetFinalizer}, {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, + {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName}, {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetworkUtils.cpp index e2af87ee1adf..750810840bde 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetworkUtils.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "NetUtils" +#define LOG_TAG "NetworkUtils" #include <vector> @@ -123,15 +123,6 @@ static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, job return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd)); } -static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket) -{ - return (jboolean) !protectFromVpn(socket); -} - -static jboolean android_net_utils_protectFromVpnWithFd(JNIEnv *env, jobject thiz, jobject javaFd) { - return android_net_utils_protectFromVpn(env, thiz, AFileDescriptor_getFD(env, javaFd)); -} - static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId) { return (jboolean) !queryUserAccess(uid, netId); @@ -276,8 +267,6 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess }, { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork }, - { "protectFromVpn", "(I)Z", (void*) android_net_utils_protectFromVpn }, - { "protectFromVpn", "(Ljava/io/FileDescriptor;)Z", (void*) android_net_utils_protectFromVpnWithFd }, { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess }, { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter }, { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter }, diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 1c78750f3610..5e142fd10de0 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -45,6 +45,12 @@ static const char* toString(bool value) { 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; @@ -70,6 +76,14 @@ static std::string addPrefix(std::string str, std::string_view prefix) { 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, @@ -106,9 +120,16 @@ private: return mInputConsumer.getChannel()->getName(); } - virtual int handleEvent(int receiveFd, int events, void* data) override; + HandleEventResponse 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) @@ -179,10 +200,61 @@ void NativeInputEventReceiver::setFdEvents(int events) { } } +/** + * Receiver's primary role is to receive input events, but it has an additional duty of sending + * 'ack' for events (using the call 'finishInputEvent'). + * + * If we are looking at the communication between InputPublisher and InputConsumer, we can say that + * from the InputConsumer's perspective, InputMessage's that are sent from publisher to consumer are + * called 'inbound / incoming' events, and the InputMessage's sent from InputConsumer to + * InputPublisher are 'outbound / outgoing' events. + * + * NativeInputEventReceiver owns (and acts like) an InputConsumer. So the finish events are outbound + * from InputEventReceiver (and will be sent to the InputPublisher). + * + * In this function, send as many events from 'mFinishQueue' as possible across the socket to the + * InputPublisher. If no events are remaining, let the looper know so that it doesn't wake up + * unnecessarily. + */ +HandleEventResponse NativeInputEventReceiver::processOutboundEvents() { + while (!mFinishQueue.empty()) { + const Finish& finish = *mFinishQueue.begin(); + status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); + if (status == OK) { + // Successful send. Erase the entry and keep trying to send more + mFinishQueue.erase(mFinishQueue.begin()); + continue; + } + + // Publisher is busy, try again later. Keep this entry (do not erase) + if (status == WOULD_BLOCK) { + if (kDebugDispatchCycle) { + ALOGD("channel '%s' ~ Remaining outbound events: %zu.", + getInputChannelName().c_str(), mFinishQueue.size()); + } + return HandleEventResponse::KEEP_CALLBACK; // try again later + } + + // Some other error. Give up + ALOGW("Failed to send outbound event on channel '%s'. status=%d", + getInputChannelName().c_str(), status); + if (status != DEAD_OBJECT) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + std::string message = + android::base::StringPrintf("Failed to send outbound event. status=%d", + status); + jniThrowRuntimeException(env, message.c_str()); + mMessageQueue->raiseAndClearException(env, "finishInputEvent"); + } + return HandleEventResponse::REMOVE_CALLBACK; + } + + // The queue is now empty. Tell looper there's no more output to expect. + setFdEvents(ALOOPER_EVENT_INPUT); + return HandleEventResponse::KEEP_CALLBACK; +} + 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 @@ -191,56 +263,25 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. " "events=0x%x", getInputChannelName().c_str(), events); } - return REMOVE_CALLBACK; + return toUnderlying(HandleEventResponse::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 ? KEEP_CALLBACK : REMOVE_CALLBACK; + return status == OK || status == NO_MEMORY + ? toUnderlying(HandleEventResponse::KEEP_CALLBACK) + : toUnderlying(HandleEventResponse::REMOVE_CALLBACK); } if (events & ALOOPER_EVENT_OUTPUT) { - for (size_t i = 0; i < mFinishQueue.size(); i++) { - const Finish& finish = mFinishQueue[i]; - status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); - if (status != OK) { - mFinishQueue.erase(mFinishQueue.begin(), mFinishQueue.begin() + i); - - if (status == WOULD_BLOCK) { - if (kDebugDispatchCycle) { - ALOGD("channel '%s' ~ Sent %zu queued finish events; %zu left.", - getInputChannelName().c_str(), i, mFinishQueue.size()); - } - return KEEP_CALLBACK; // try again later - } - - ALOGW("Failed to send finished signal on channel '%s'. status=%d", - getInputChannelName().c_str(), status); - if (status != DEAD_OBJECT) { - JNIEnv* env = AndroidRuntime::getJNIEnv(); - std::string message = - android::base::StringPrintf("Failed to finish input event. status=%d", - status); - jniThrowRuntimeException(env, message.c_str()); - mMessageQueue->raiseAndClearException(env, "finishInputEvent"); - } - return REMOVE_CALLBACK; - } - } - if (kDebugDispatchCycle) { - ALOGD("channel '%s' ~ Sent %zu queued finish events; none left.", - getInputChannelName().c_str(), mFinishQueue.size()); - } - mFinishQueue.clear(); - setFdEvents(ALOOPER_EVENT_INPUT); - return KEEP_CALLBACK; + return toUnderlying(processOutboundEvents()); } ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " "events=0x%x", getInputChannelName().c_str(), events); - return KEEP_CALLBACK; + return toUnderlying(HandleEventResponse::KEEP_CALLBACK); } status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 451ea93349f7..cbf4481bd2f1 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -27,6 +27,7 @@ #include <android-base/chrono_utils.h> #include <android/graphics/region.h> #include <android/gui/BnScreenCaptureListener.h> +#include <android/hardware/display/IDeviceProductInfoConstants.h> #include <android/os/IInputConstants.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_hardware_HardwareBuffer.h> @@ -1022,16 +1023,24 @@ static jobject convertDeviceProductInfoToJavaObject( } else { LOG_FATAL("Unknown alternative for variant DeviceProductInfo::ManufactureOrModelDate"); } - auto relativeAddress = env->NewIntArray(info->relativeAddress.size()); - auto relativeAddressData = env->GetIntArrayElements(relativeAddress, nullptr); - for (int i = 0; i < info->relativeAddress.size(); i++) { - relativeAddressData[i] = info->relativeAddress[i]; + jint connectionToSinkType; + // Relative address maps to HDMI physical address. All addresses are 4 digits long allowing + // for a 5–device-deep hierarchy. For more information, refer: + // Section 8.7 - Physical Address of HDMI Specification Version 1.3a + using android::hardware::display::IDeviceProductInfoConstants; + if (info->relativeAddress.size() != 4) { + connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_UNKNOWN; + } else if (info->relativeAddress[0] == 0) { + connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_BUILT_IN; + } else if (info->relativeAddress[1] == 0) { + connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_DIRECT; + } else { + connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_TRANSITIVE; } - env->ReleaseIntArrayElements(relativeAddress, relativeAddressData, 0); return env->NewObject(gDeviceProductInfoClassInfo.clazz, gDeviceProductInfoClassInfo.ctor, name, manufacturerPnpId, productId, modelYear, manufactureDate, - relativeAddress); + connectionToSinkType); } static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) { @@ -1970,7 +1979,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) "Ljava/lang/String;" "Ljava/lang/Integer;" "Landroid/hardware/display/DeviceProductInfo$ManufactureDate;" - "[I)V"); + "I)V"); jclass deviceProductInfoManufactureDateClazz = FindClassOrDie(env, "android/hardware/display/DeviceProductInfo$ManufactureDate"); diff --git a/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp b/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp index 10fc18dcd386..980e12d0bb40 100644 --- a/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp +++ b/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <android/file_descriptor_jni.h> + #include "NetdClient.h" #include "core_jni_helpers.h" #include "jni.h" @@ -24,9 +26,20 @@ static void android_net_utils_setAllowNetworkingForProcess(JNIEnv *env, jobject setAllowNetworkingForProcess(hasConnectivity == JNI_TRUE); } +static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket) { + return (jboolean)!protectFromVpn(socket); +} + +static jboolean android_net_utils_protectFromVpnWithFd(JNIEnv *env, jobject thiz, jobject javaFd) { + return android_net_utils_protectFromVpn(env, thiz, AFileDescriptor_getFD(env, javaFd)); +} + static const JNINativeMethod gNetworkUtilMethods[] = { {"setAllowNetworkingForProcess", "(Z)V", (void *)android_net_utils_setAllowNetworkingForProcess}, + {"protectFromVpn", "(I)Z", (void *)android_net_utils_protectFromVpn}, + {"protectFromVpn", "(Ljava/io/FileDescriptor;)Z", + (void *)android_net_utils_protectFromVpnWithFd}, }; int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv *env) { diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index c9062d8a50bc..836074f1d5f7 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -304,15 +304,14 @@ static std::array<UsapTableEntry, USAP_POOL_SIZE_MAX_LIMIT> gUsapTable; static FileDescriptorTable* gOpenFdTable = nullptr; // Must match values in com.android.internal.os.Zygote. -// Note that there are gaps in the constants: -// This is to further keep the values consistent with IVold.aidl +// The values should be consistent with IVold.aidl enum MountExternalKind { MOUNT_EXTERNAL_NONE = 0, MOUNT_EXTERNAL_DEFAULT = 1, - MOUNT_EXTERNAL_INSTALLER = 5, - MOUNT_EXTERNAL_PASS_THROUGH = 7, - MOUNT_EXTERNAL_ANDROID_WRITABLE = 8, - MOUNT_EXTERNAL_COUNT = 9 + MOUNT_EXTERNAL_INSTALLER = 2, + MOUNT_EXTERNAL_PASS_THROUGH = 3, + MOUNT_EXTERNAL_ANDROID_WRITABLE = 4, + MOUNT_EXTERNAL_COUNT = 5 }; // Must match values in com.android.internal.os.Zygote. @@ -1401,16 +1400,15 @@ static void insertPackagesToMergedList(JNIEnv* env, } static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, - jobjectArray whitelisted_data_info_list, uid_t uid, const char* process_name, - jstring managed_nice_name, fail_fn_t fail_fn) { - - std::vector<std::string> merged_data_info_list; - insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list, - process_name, managed_nice_name, fail_fn); - insertPackagesToMergedList(env, merged_data_info_list, whitelisted_data_info_list, - process_name, managed_nice_name, fail_fn); + jobjectArray allowlisted_data_info_list, uid_t uid, + const char* process_name, jstring managed_nice_name, fail_fn_t fail_fn) { + std::vector<std::string> merged_data_info_list; + insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list, process_name, + managed_nice_name, fail_fn); + insertPackagesToMergedList(env, merged_data_info_list, allowlisted_data_info_list, process_name, + managed_nice_name, fail_fn); - isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn); + isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn); } /** @@ -1511,240 +1509,242 @@ static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list, } // Utility routine to specialize a zygote child process. -static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, - jint runtime_flags, jobjectArray rlimits, - jlong permitted_capabilities, jlong effective_capabilities, - jint mount_external, jstring managed_se_info, - jstring managed_nice_name, bool is_system_server, - bool is_child_zygote, jstring managed_instruction_set, - jstring managed_app_data_dir, bool is_top_app, - jobjectArray pkg_data_info_list, - jobjectArray whitelisted_data_info_list, - bool mount_data_dirs, bool mount_storage_dirs) { - const char* process_name = is_system_server ? "system_server" : "zygote"; - auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1); - auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); +static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, + jobjectArray rlimits, jlong permitted_capabilities, + jlong effective_capabilities, jint mount_external, + jstring managed_se_info, jstring managed_nice_name, + bool is_system_server, bool is_child_zygote, + jstring managed_instruction_set, jstring managed_app_data_dir, + bool is_top_app, jobjectArray pkg_data_info_list, + jobjectArray allowlisted_data_info_list, bool mount_data_dirs, + bool mount_storage_dirs) { + const char* process_name = is_system_server ? "system_server" : "zygote"; + auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1); + auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); + + auto se_info = extract_fn(managed_se_info); + auto nice_name = extract_fn(managed_nice_name); + auto instruction_set = extract_fn(managed_instruction_set); + auto app_data_dir = extract_fn(managed_app_data_dir); + + // Keep capabilities across UID change, unless we're staying root. + if (uid != 0) { + EnableKeepCapabilities(fail_fn); + } - auto se_info = extract_fn(managed_se_info); - auto nice_name = extract_fn(managed_nice_name); - auto instruction_set = extract_fn(managed_instruction_set); - auto app_data_dir = extract_fn(managed_app_data_dir); + SetInheritable(permitted_capabilities, fail_fn); - // Keep capabilities across UID change, unless we're staying root. - if (uid != 0) { - EnableKeepCapabilities(fail_fn); - } + DropCapabilitiesBoundingSet(fail_fn); - SetInheritable(permitted_capabilities, fail_fn); + bool need_pre_initialize_native_bridge = !is_system_server && instruction_set.has_value() && + android::NativeBridgeAvailable() && + // Native bridge may be already initialized if this + // is an app forked from app-zygote. + !android::NativeBridgeInitialized() && + android::NeedsNativeBridge(instruction_set.value().c_str()); - DropCapabilitiesBoundingSet(fail_fn); + MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn); - bool need_pre_initialize_native_bridge = - !is_system_server && - instruction_set.has_value() && - android::NativeBridgeAvailable() && - // Native bridge may be already initialized if this - // is an app forked from app-zygote. - !android::NativeBridgeInitialized() && - android::NeedsNativeBridge(instruction_set.value().c_str()); + // Make sure app is running in its own mount namespace before isolating its data directories. + ensureInAppMountNamespace(fail_fn); - MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn); + // Sandbox data and jit profile directories by overlaying a tmpfs on those dirs and bind + // mount all related packages separately. + if (mount_data_dirs) { + isolateAppData(env, pkg_data_info_list, allowlisted_data_info_list, uid, process_name, + managed_nice_name, fail_fn); + isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); + } + // MOUNT_EXTERNAL_INSTALLER, MOUNT_EXTERNAL_PASS_THROUGH, MOUNT_EXTERNAL_ANDROID_WRITABLE apps + // will have mount_storage_dirs == false here (set by ProcessList.needsStorageDataIsolation()), + // and hence they won't bind mount storage dirs. + if (mount_storage_dirs) { + BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, + fail_fn); + } - // Make sure app is running in its own mount namespace before isolating its data directories. - ensureInAppMountNamespace(fail_fn); + // If this zygote isn't root, it won't be able to create a process group, + // since the directory is owned by root. + if (!is_system_server && getuid() == 0) { + const int rc = createProcessGroup(uid, getpid()); + if (rc == -EROFS) { + ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?"); + } else if (rc != 0) { + ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc)); + } + } - // Sandbox data and jit profile directories by overlaying a tmpfs on those dirs and bind - // mount all related packages separately. - if (mount_data_dirs) { - isolateAppData(env, pkg_data_info_list, whitelisted_data_info_list, - uid, process_name, managed_nice_name, fail_fn); - isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); - } - // MOUNT_EXTERNAL_INSTALLER, MOUNT_EXTERNAL_PASS_THROUGH, MOUNT_EXTERNAL_ANDROID_WRITABLE apps - // will have mount_storage_dirs == false here (set by ProcessList.needsStorageDataIsolation()), - // and hence they won't bind mount storage dirs. - if (mount_storage_dirs) { - BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); - } + SetGids(env, gids, is_child_zygote, fail_fn); + SetRLimits(env, rlimits, fail_fn); - // If this zygote isn't root, it won't be able to create a process group, - // since the directory is owned by root. - if (!is_system_server && getuid() == 0) { - const int rc = createProcessGroup(uid, getpid()); - if (rc == -EROFS) { - ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?"); - } else if (rc != 0) { - ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc)); + if (need_pre_initialize_native_bridge) { + // Due to the logic behind need_pre_initialize_native_bridge we know that + // instruction_set contains a value. + android::PreInitializeNativeBridge(app_data_dir.has_value() ? app_data_dir.value().c_str() + : nullptr, + instruction_set.value().c_str()); } - } - SetGids(env, gids, is_child_zygote, fail_fn); - SetRLimits(env, rlimits, fail_fn); - - if (need_pre_initialize_native_bridge) { - // Due to the logic behind need_pre_initialize_native_bridge we know that - // instruction_set contains a value. - android::PreInitializeNativeBridge( - app_data_dir.has_value() ? app_data_dir.value().c_str() : nullptr, - instruction_set.value().c_str()); - } + if (setresgid(gid, gid, gid) == -1) { + fail_fn(CREATE_ERROR("setresgid(%d) failed: %s", gid, strerror(errno))); + } - if (setresgid(gid, gid, gid) == -1) { - fail_fn(CREATE_ERROR("setresgid(%d) failed: %s", gid, strerror(errno))); - } + // Must be called when the new process still has CAP_SYS_ADMIN, in this case, + // before changing uid from 0, which clears capabilities. The other + // alternative is to call prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that + // breaks SELinux domain transition (see b/71859146). As the result, + // privileged syscalls used below still need to be accessible in app process. + SetUpSeccompFilter(uid, is_child_zygote); - // Must be called when the new process still has CAP_SYS_ADMIN, in this case, - // before changing uid from 0, which clears capabilities. The other - // alternative is to call prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that - // breaks SELinux domain transition (see b/71859146). As the result, - // privileged syscalls used below still need to be accessible in app process. - SetUpSeccompFilter(uid, is_child_zygote); + // Must be called before losing the permission to set scheduler policy. + SetSchedulerPolicy(fail_fn, is_top_app); - // Must be called before losing the permission to set scheduler policy. - SetSchedulerPolicy(fail_fn, is_top_app); + if (setresuid(uid, uid, uid) == -1) { + fail_fn(CREATE_ERROR("setresuid(%d) failed: %s", uid, strerror(errno))); + } - if (setresuid(uid, uid, uid) == -1) { - fail_fn(CREATE_ERROR("setresuid(%d) failed: %s", uid, strerror(errno))); - } + // The "dumpable" flag of a process, which controls core dump generation, is + // overwritten by the value in /proc/sys/fs/suid_dumpable when the effective + // user or group ID changes. See proc(5) for possible values. In most cases, + // the value is 0, so core dumps are disabled for zygote children. However, + // when running in a Chrome OS container, the value is already set to 2, + // which allows the external crash reporter to collect all core dumps. Since + // only system crashes are interested, core dump is disabled for app + // processes. This also ensures compliance with CTS. + int dumpable = prctl(PR_GET_DUMPABLE); + if (dumpable == -1) { + ALOGE("prctl(PR_GET_DUMPABLE) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "prctl(PR_GET_DUMPABLE) failed"); + } - // The "dumpable" flag of a process, which controls core dump generation, is - // overwritten by the value in /proc/sys/fs/suid_dumpable when the effective - // user or group ID changes. See proc(5) for possible values. In most cases, - // the value is 0, so core dumps are disabled for zygote children. However, - // when running in a Chrome OS container, the value is already set to 2, - // which allows the external crash reporter to collect all core dumps. Since - // only system crashes are interested, core dump is disabled for app - // processes. This also ensures compliance with CTS. - int dumpable = prctl(PR_GET_DUMPABLE); - if (dumpable == -1) { - ALOGE("prctl(PR_GET_DUMPABLE) failed: %s", strerror(errno)); - RuntimeAbort(env, __LINE__, "prctl(PR_GET_DUMPABLE) failed"); - } + if (dumpable == 2 && uid >= AID_APP) { + if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1) { + ALOGE("prctl(PR_SET_DUMPABLE, 0) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 0) failed"); + } + } - if (dumpable == 2 && uid >= AID_APP) { - if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1) { - ALOGE("prctl(PR_SET_DUMPABLE, 0) failed: %s", strerror(errno)); - RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 0) failed"); + // Set process properties to enable debugging if required. + if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_JDWP) != 0) { + EnableDebugger(); + } + if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) { + // simpleperf needs the process to be dumpable to profile it. + if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) { + ALOGE("prctl(PR_SET_DUMPABLE) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 1) failed"); + } } - } - // Set process properties to enable debugging if required. - if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_JDWP) != 0) { - EnableDebugger(); - } - if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) { - // simpleperf needs the process to be dumpable to profile it. - if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) { - ALOGE("prctl(PR_SET_DUMPABLE) failed: %s", strerror(errno)); - RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 1) failed"); + HeapTaggingLevel heap_tagging_level; + switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) { + case RuntimeFlags::MEMORY_TAG_LEVEL_TBI: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI; + break; + case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC; + break; + case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC; + break; + default: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; + break; + } + mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level); + + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART + // runtime. + runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK; + + // Avoid heap zero initialization for applications without MTE. Zero init may + // cause app compat problems, use more memory, or reduce performance. While it + // would be nice to have them for apps, we will have to wait until they are + // proven out, have more efficient hardware, and/or apply them only to new + // applications. + if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) { + mallopt(M_BIONIC_ZERO_INIT, 0); } - } - HeapTaggingLevel heap_tagging_level; - switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) { - case RuntimeFlags::MEMORY_TAG_LEVEL_TBI: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI; - break; - case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC; - break; - case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC; - break; - default: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; - break; - } - mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level); + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART + // runtime. + runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT; - // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime. - runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK; + bool forceEnableGwpAsan = false; + switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) { + default: + case RuntimeFlags::GWP_ASAN_LEVEL_NEVER: + break; + case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS: + forceEnableGwpAsan = true; + [[fallthrough]]; + case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY: + android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan)); + } + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART + // runtime. + runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK; + + if (NeedsNoRandomizeWorkaround()) { + // Work around ARM kernel ASLR lossage (http://b/5817320). + int old_personality = personality(0xffffffff); + int new_personality = personality(old_personality | ADDR_NO_RANDOMIZE); + if (new_personality == -1) { + ALOGW("personality(%d) failed: %s", new_personality, strerror(errno)); + } + } - // Avoid heap zero initialization for applications without MTE. Zero init may - // cause app compat problems, use more memory, or reduce performance. While it - // would be nice to have them for apps, we will have to wait until they are - // proven out, have more efficient hardware, and/or apply them only to new - // applications. - if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) { - mallopt(M_BIONIC_ZERO_INIT, 0); - } + SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, + fail_fn); - // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime. - runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT; + __android_log_close(); + AStatsSocket_close(); - bool forceEnableGwpAsan = false; - switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) { - default: - case RuntimeFlags::GWP_ASAN_LEVEL_NEVER: - break; - case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS: - forceEnableGwpAsan = true; - [[fallthrough]]; - case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY: - android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan)); - } - // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime. - runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK; + const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr; + const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr; - if (NeedsNoRandomizeWorkaround()) { - // Work around ARM kernel ASLR lossage (http://b/5817320). - int old_personality = personality(0xffffffff); - int new_personality = personality(old_personality | ADDR_NO_RANDOMIZE); - if (new_personality == -1) { - ALOGW("personality(%d) failed: %s", new_personality, strerror(errno)); + if (selinux_android_setcontext(uid, is_system_server, se_info_ptr, nice_name_ptr) == -1) { + fail_fn(CREATE_ERROR("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid, + is_system_server, se_info_ptr, nice_name_ptr)); } - } - - SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn); - - __android_log_close(); - AStatsSocket_close(); - - const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr; - const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr; - if (selinux_android_setcontext(uid, is_system_server, se_info_ptr, nice_name_ptr) == -1) { - fail_fn(CREATE_ERROR("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", - uid, is_system_server, se_info_ptr, nice_name_ptr)); - } + // Make it easier to debug audit logs by setting the main thread's name to the + // nice name rather than "app_process". + if (nice_name.has_value()) { + SetThreadName(nice_name.value()); + } else if (is_system_server) { + SetThreadName("system_server"); + } - // Make it easier to debug audit logs by setting the main thread's name to the - // nice name rather than "app_process". - if (nice_name.has_value()) { - SetThreadName(nice_name.value()); - } else if (is_system_server) { - SetThreadName("system_server"); - } + // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers). + UnsetChldSignalHandler(); - // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers). - UnsetChldSignalHandler(); + if (is_system_server) { + env->CallStaticVoidMethod(gZygoteClass, gCallPostForkSystemServerHooks, runtime_flags); + if (env->ExceptionCheck()) { + fail_fn("Error calling post fork system server hooks."); + } - if (is_system_server) { - env->CallStaticVoidMethod(gZygoteClass, gCallPostForkSystemServerHooks, runtime_flags); - if (env->ExceptionCheck()) { - fail_fn("Error calling post fork system server hooks."); + // TODO(b/117874058): Remove hardcoded label here. + static const char* kSystemServerLabel = "u:r:system_server:s0"; + if (selinux_android_setcon(kSystemServerLabel) != 0) { + fail_fn(CREATE_ERROR("selinux_android_setcon(%s)", kSystemServerLabel)); + } } - // TODO(oth): Remove hardcoded label here (b/117874058). - static const char* kSystemServerLabel = "u:r:system_server:s0"; - if (selinux_android_setcon(kSystemServerLabel) != 0) { - fail_fn(CREATE_ERROR("selinux_android_setcon(%s)", kSystemServerLabel)); + if (is_child_zygote) { + initUnsolSocketToSystemServer(); } - } - if (is_child_zygote) { - initUnsolSocketToSystemServer(); - } + env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, + is_system_server, is_child_zygote, managed_instruction_set); - env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, - is_system_server, is_child_zygote, managed_instruction_set); + // Reset the process priority to the default value. + setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT); - // Reset the process priority to the default value. - setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT); - - if (env->ExceptionCheck()) { - fail_fn("Error calling post fork hooks."); - } + if (env->ExceptionCheck()) { + fail_fn("Error calling post fork hooks."); + } } static uint64_t GetEffectiveCapabilityMask(JNIEnv* env) { @@ -2069,12 +2069,11 @@ static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jcl NO_PAC_FUNC static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( - JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, - jint runtime_flags, jobjectArray rlimits, - jint mount_external, jstring se_info, jstring nice_name, + JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, + jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, - jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, + jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); @@ -2109,14 +2108,11 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( pid_t pid = zygote::ForkCommon(env, false, fds_to_close, fds_to_ignore, true); if (pid == 0) { - SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, - capabilities, capabilities, - mount_external, se_info, nice_name, false, - is_child_zygote == JNI_TRUE, instruction_set, app_data_dir, - is_top_app == JNI_TRUE, pkg_data_info_list, - whitelisted_data_info_list, - mount_data_dirs == JNI_TRUE, - mount_storage_dirs == JNI_TRUE); + SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, + mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE, + instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list, + allowlisted_data_info_list, mount_data_dirs == JNI_TRUE, + mount_storage_dirs == JNI_TRUE); } return pid; } @@ -2148,12 +2144,11 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( if (pid == 0) { // System server prcoess does not need data isolation so no need to // know pkg_data_info_list. - SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, - permitted_capabilities, effective_capabilities, - MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true, + SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, + effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true, false, nullptr, nullptr, /* is_top_app= */ false, /* pkg_data_info_list */ nullptr, - /* whitelisted_data_info_list */ nullptr, false, false); + /* allowlisted_data_info_list */ nullptr, false, false); } else if (pid > 0) { // The zygote process checks whether the child process has died or not. ALOGI("System server process %d has been created", pid); @@ -2261,7 +2256,7 @@ static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( if (!path_cstr) { RuntimeAbort(env, __LINE__, "path_cstr == nullptr"); } - FileDescriptorWhitelist::Get()->Allow(path_cstr); + FileDescriptorAllowlist::Get()->Allow(path_cstr); } static void com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter( @@ -2296,20 +2291,19 @@ static void com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter( * @param is_top_app If the process is for top (high priority) application */ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess( - JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, - jint runtime_flags, jobjectArray rlimits, - jint mount_external, jstring se_info, jstring nice_name, - jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, - jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, - jboolean mount_data_dirs, jboolean mount_storage_dirs) { - jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); - - SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, - capabilities, capabilities, - mount_external, se_info, nice_name, false, - is_child_zygote == JNI_TRUE, instruction_set, app_data_dir, - is_top_app == JNI_TRUE, pkg_data_info_list, whitelisted_data_info_list, - mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE); + JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, + jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, + jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, + jboolean is_top_app, jobjectArray pkg_data_info_list, + jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs, + jboolean mount_storage_dirs) { + jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); + + SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, + mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE, + instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list, + allowlisted_data_info_list, mount_data_dirs == JNI_TRUE, + mount_storage_dirs == JNI_TRUE); } /** diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 06a71cb22672..7fa627b3f809 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -31,8 +31,8 @@ #include <android-base/stringprintf.h> #include <android-base/strings.h> -// Static whitelist of open paths that the zygote is allowed to keep open. -static const char* kPathWhitelist[] = { +// Static allowlist of open paths that the zygote is allowed to keep open. +static const char* kPathAllowlist[] = { "/dev/null", "/dev/socket/zygote", "/dev/socket/zygote_secondary", @@ -53,118 +53,114 @@ static const char* kPathWhitelist[] = { static const char kFdPath[] = "/proc/self/fd"; // static -FileDescriptorWhitelist* FileDescriptorWhitelist::Get() { - if (instance_ == nullptr) { - instance_ = new FileDescriptorWhitelist(); - } - return instance_; +FileDescriptorAllowlist* FileDescriptorAllowlist::Get() { + if (instance_ == nullptr) { + instance_ = new FileDescriptorAllowlist(); + } + return instance_; } static bool IsArtMemfd(const std::string& path) { return android::base::StartsWith(path, "/memfd:/boot-image-methods.art"); } -bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { - // Check the static whitelist path. - for (const auto& whitelist_path : kPathWhitelist) { - if (path == whitelist_path) - return true; - } +bool FileDescriptorAllowlist::IsAllowed(const std::string& path) const { + // Check the static allowlist path. + for (const auto& allowlist_path : kPathAllowlist) { + if (path == allowlist_path) return true; + } - // Check any paths added to the dynamic whitelist. - for (const auto& whitelist_path : whitelist_) { - if (path == whitelist_path) - return true; - } + // Check any paths added to the dynamic allowlist. + for (const auto& allowlist_path : allowlist_) { + if (path == allowlist_path) return true; + } - // Framework jars are allowed. - static const char* kFrameworksPrefix[] = { - "/system/framework/", - "/system_ext/framework/", - }; + // Framework jars are allowed. + static const char* kFrameworksPrefix[] = { + "/system/framework/", + "/system_ext/framework/", + }; - static const char* kJarSuffix = ".jar"; + static const char* kJarSuffix = ".jar"; - for (const auto& frameworks_prefix : kFrameworksPrefix) { - if (android::base::StartsWith(path, frameworks_prefix) - && android::base::EndsWith(path, kJarSuffix)) { - return true; + for (const auto& frameworks_prefix : kFrameworksPrefix) { + if (android::base::StartsWith(path, frameworks_prefix) && + android::base::EndsWith(path, kJarSuffix)) { + return true; + } } - } - // Jars from APEXes are allowed. This matches /apex/**/javalib/*.jar. - static const char* kApexPrefix = "/apex/"; - static const char* kApexJavalibPathSuffix = "/javalib"; - if (android::base::StartsWith(path, kApexPrefix) && android::base::EndsWith(path, kJarSuffix) && - android::base::EndsWith(android::base::Dirname(path), kApexJavalibPathSuffix)) { - return true; - } + // Jars from APEXes are allowed. This matches /apex/**/javalib/*.jar. + static const char* kApexPrefix = "/apex/"; + static const char* kApexJavalibPathSuffix = "/javalib"; + if (android::base::StartsWith(path, kApexPrefix) && android::base::EndsWith(path, kJarSuffix) && + android::base::EndsWith(android::base::Dirname(path), kApexJavalibPathSuffix)) { + return true; + } - // the in-memory file created by ART through memfd_create is allowed. - if (IsArtMemfd(path)) { - return true; - } + // the in-memory file created by ART through memfd_create is allowed. + if (IsArtMemfd(path)) { + return true; + } - // Whitelist files needed for Runtime Resource Overlay, like these: - // /system/vendor/overlay/framework-res.apk - // /system/vendor/overlay-subdir/pg/framework-res.apk - // /vendor/overlay/framework-res.apk - // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk - // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap - // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap - // See AssetManager.cpp for more details on overlay-subdir. - static const char* kOverlayDir = "/system/vendor/overlay/"; - static const char* kVendorOverlayDir = "/vendor/overlay"; - static const char* kVendorOverlaySubdir = "/system/vendor/overlay-subdir/"; - static const char* kSystemProductOverlayDir = "/system/product/overlay/"; - static const char* kProductOverlayDir = "/product/overlay"; - static const char* kSystemSystemExtOverlayDir = "/system/system_ext/overlay/"; - static const char* kSystemExtOverlayDir = "/system_ext/overlay"; - static const char* kSystemOdmOverlayDir = "/system/odm/overlay"; - static const char* kOdmOverlayDir = "/odm/overlay"; - static const char* kSystemOemOverlayDir = "/system/oem/overlay"; - static const char* kOemOverlayDir = "/oem/overlay"; - static const char* kApkSuffix = ".apk"; - - if ((android::base::StartsWith(path, kOverlayDir) - || android::base::StartsWith(path, kVendorOverlaySubdir) - || android::base::StartsWith(path, kVendorOverlayDir) - || android::base::StartsWith(path, kSystemProductOverlayDir) - || android::base::StartsWith(path, kProductOverlayDir) - || android::base::StartsWith(path, kSystemSystemExtOverlayDir) - || android::base::StartsWith(path, kSystemExtOverlayDir) - || android::base::StartsWith(path, kSystemOdmOverlayDir) - || android::base::StartsWith(path, kOdmOverlayDir) - || android::base::StartsWith(path, kSystemOemOverlayDir) - || android::base::StartsWith(path, kOemOverlayDir)) - && android::base::EndsWith(path, kApkSuffix) - && path.find("/../") == std::string::npos) { - return true; - } + // Allowlist files needed for Runtime Resource Overlay, like these: + // /system/vendor/overlay/framework-res.apk + // /system/vendor/overlay-subdir/pg/framework-res.apk + // /vendor/overlay/framework-res.apk + // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk + // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap + // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap + // See AssetManager.cpp for more details on overlay-subdir. + static const char* kOverlayDir = "/system/vendor/overlay/"; + static const char* kVendorOverlayDir = "/vendor/overlay"; + static const char* kVendorOverlaySubdir = "/system/vendor/overlay-subdir/"; + static const char* kSystemProductOverlayDir = "/system/product/overlay/"; + static const char* kProductOverlayDir = "/product/overlay"; + static const char* kSystemSystemExtOverlayDir = "/system/system_ext/overlay/"; + static const char* kSystemExtOverlayDir = "/system_ext/overlay"; + static const char* kSystemOdmOverlayDir = "/system/odm/overlay"; + static const char* kOdmOverlayDir = "/odm/overlay"; + static const char* kSystemOemOverlayDir = "/system/oem/overlay"; + static const char* kOemOverlayDir = "/oem/overlay"; + static const char* kApkSuffix = ".apk"; + + if ((android::base::StartsWith(path, kOverlayDir) || + android::base::StartsWith(path, kVendorOverlaySubdir) || + android::base::StartsWith(path, kVendorOverlayDir) || + android::base::StartsWith(path, kSystemProductOverlayDir) || + android::base::StartsWith(path, kProductOverlayDir) || + android::base::StartsWith(path, kSystemSystemExtOverlayDir) || + android::base::StartsWith(path, kSystemExtOverlayDir) || + android::base::StartsWith(path, kSystemOdmOverlayDir) || + android::base::StartsWith(path, kOdmOverlayDir) || + android::base::StartsWith(path, kSystemOemOverlayDir) || + android::base::StartsWith(path, kOemOverlayDir)) && + android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) { + return true; + } - static const char* kOverlayIdmapPrefix = "/data/resource-cache/"; - static const char* kOverlayIdmapSuffix = ".apk@idmap"; - if (android::base::StartsWith(path, kOverlayIdmapPrefix) - && android::base::EndsWith(path, kOverlayIdmapSuffix) - && path.find("/../") == std::string::npos) { - return true; - } + static const char* kOverlayIdmapPrefix = "/data/resource-cache/"; + static const char* kOverlayIdmapSuffix = ".apk@idmap"; + if (android::base::StartsWith(path, kOverlayIdmapPrefix) && + android::base::EndsWith(path, kOverlayIdmapSuffix) && + path.find("/../") == std::string::npos) { + return true; + } - // All regular files that are placed under this path are whitelisted automatically. - static const char* kZygoteWhitelistPath = "/vendor/zygote_whitelist/"; - if (android::base::StartsWith(path, kZygoteWhitelistPath) - && path.find("/../") == std::string::npos) { - return true; - } + // All regular files that are placed under this path are allowlisted + // automatically. The directory name is maintained for compatibility. + static const char* kZygoteAllowlistPath = "/vendor/zygote_whitelist/"; + if (android::base::StartsWith(path, kZygoteAllowlistPath) && + path.find("/../") == std::string::npos) { + return true; + } - return false; + return false; } -FileDescriptorWhitelist::FileDescriptorWhitelist() - : whitelist_() { -} +FileDescriptorAllowlist::FileDescriptorAllowlist() : allowlist_() {} -FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr; +FileDescriptorAllowlist* FileDescriptorAllowlist::instance_ = nullptr; // Keeps track of all relevant information (flags, offset etc.) of an // open zygote file descriptor. @@ -217,7 +213,7 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) fail_fn(android::base::StringPrintf("Unable to stat %d", fd)); } - const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get(); + const FileDescriptorAllowlist* allowlist = FileDescriptorAllowlist::Get(); if (S_ISSOCK(f_stat.st_mode)) { std::string socket_name; @@ -225,16 +221,15 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) fail_fn("Unable to get socket name"); } - if (!whitelist->IsAllowed(socket_name)) { - fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)", - socket_name.c_str(), - fd)); + if (!allowlist->IsAllowed(socket_name)) { + fail_fn(android::base::StringPrintf("Socket name not allowlisted : %s (fd=%d)", + socket_name.c_str(), fd)); } return new FileDescriptorInfo(fd); } - // We only handle whitelisted regular files and character devices. Whitelisted + // We only handle allowlisted regular files and character devices. Allowlisted // character devices must provide a guarantee of sensible behaviour when // reopened. // @@ -268,8 +263,8 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) strerror(errno))); } - if (!whitelist->IsAllowed(file_path)) { - fail_fn(android::base::StringPrintf("Not whitelisted (%d): %s", fd, file_path.c_str())); + if (!allowlist->IsAllowed(file_path)) { + fail_fn(android::base::StringPrintf("Not allowlisted (%d): %s", fd, file_path.c_str())); } // File descriptor flags : currently on FD_CLOEXEC. We can set these diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h index 2caf1575981a..14c318e8e84a 100644 --- a/core/jni/fd_utils.h +++ b/core/jni/fd_utils.h @@ -33,42 +33,40 @@ class FileDescriptorInfo; // This type is duplicated in com_android_internal_os_Zygote.cpp typedef const std::function<void(std::string)>& fail_fn_t; -// Whitelist of open paths that the zygote is allowed to keep open. +// Allowlist of open paths that the zygote is allowed to keep open. // -// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and +// In addition to the paths listed in kPathAllowlist in file_utils.cpp, and // paths dynamically added with Allow(), all files ending with ".jar" -// under /system/framework" are whitelisted. See IsAllowed() for the canonical +// under /system/framework" are allowlisted. See IsAllowed() for the canonical // definition. // -// If the whitelisted path is associated with a regular file or a +// If the allowlisted path is associated with a regular file or a // character device, the file is reopened after a fork with the same -// offset and mode. If the whilelisted path is associated with a +// offset and mode. If the allowlisted path is associated with a // AF_UNIX socket, the socket will refer to /dev/null after each // fork, and all operations on it will fail. -class FileDescriptorWhitelist { - public: - // Lazily creates the global whitelist. - static FileDescriptorWhitelist* Get(); +class FileDescriptorAllowlist { +public: + // Lazily creates the global allowlist. + static FileDescriptorAllowlist* Get(); - // Adds a path to the whitelist. - void Allow(const std::string& path) { - whitelist_.push_back(path); - } + // Adds a path to the allowlist. + void Allow(const std::string& path) { allowlist_.push_back(path); } - // Returns true iff. a given path is whitelisted. A path is whitelisted - // if it belongs to the whitelist (see kPathWhitelist) or if it's a path - // under /system/framework that ends with ".jar" or if it is a system - // framework overlay. - bool IsAllowed(const std::string& path) const; + // Returns true iff. a given path is allowlisted. A path is allowlisted + // if it belongs to the allowlist (see kPathAllowlist) or if it's a path + // under /system/framework that ends with ".jar" or if it is a system + // framework overlay. + bool IsAllowed(const std::string& path) const; - private: - FileDescriptorWhitelist(); +private: + FileDescriptorAllowlist(); - static FileDescriptorWhitelist* instance_; + static FileDescriptorAllowlist* instance_; - std::vector<std::string> whitelist_; + std::vector<std::string> allowlist_; - DISALLOW_COPY_AND_ASSIGN(FileDescriptorWhitelist); + DISALLOW_COPY_AND_ASSIGN(FileDescriptorAllowlist); }; // A FileDescriptorTable is a collection of FileDescriptorInfo objects diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 99fd21592411..e62b5c102a59 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -15,6 +15,7 @@ per-file settings_enums.proto=tmfang@google.com ogunwale@google.com jjaggi@google.com roosa@google.com +per-file package_item_info.proto = toddke@google.com per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index ec41a47a8798..74a37cadc54c 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -628,6 +628,7 @@ message ActivityManagerServiceDumpProcessesProto { optional string tag = 3; optional int32 type = 4; optional int32 reason_code = 5; + optional int32 calling_uid = 6; } repeated PendingTempWhitelist pending_temp_whitelist = 26; diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index ec502c3c272f..f26bf7cdb6c1 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -337,7 +337,7 @@ message ActivityRecordProto { optional bool starting_displayed = 20; optional bool starting_moved = 201; optional bool visible_set_from_transferred_starting_window = 22; - repeated .android.graphics.RectProto frozen_bounds = 23; + repeated .android.graphics.RectProto frozen_bounds = 23 [deprecated=true]; optional bool visible = 24; reserved 25; // configuration_container optional IdentifierProto identifier = 26 [deprecated=true]; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d5f5d28aa7a2..072bb8799e59 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1876,15 +1876,20 @@ <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid. + <!-- @SystemApi @hide Allows applications to update Wifi/Cellular coex channels to avoid. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|role" /> + + <!-- @SystemApi @hide Allows system APK to manage country code. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MANAGE_WIFI_COUNTRY_CODE" + android:protectionLevel="signature" /> <!-- @SystemApi @hide Allows an application to manage an automotive device's application network preference as it relates to OEM_PAID and OEM_PRIVATE capable networks. @@ -2371,6 +2376,15 @@ <permission android:name="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE" android:protectionLevel="signature" /> + <!-- Must be required by a {@link android.telecom.CallDiagnosticService}, + to ensure that only the system can bind to it. + <p>Protection level: signature + @SystemApi + @hide + --> + <permission android:name="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE" + android:protectionLevel="signature" /> + <!-- Must be required by a {@link android.telecom.CallRedirectionService}, to ensure that only the system can bind to it. <p>Protection level: signature|privileged diff --git a/core/res/res/color/text_color_primary_device_default_dark.xml b/core/res/res/color/text_color_primary_device_default_dark.xml new file mode 100644 index 000000000000..90d6b07b24bd --- /dev/null +++ b/core/res/res/color/text_color_primary_device_default_dark.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. + --> +<!-- Please see primary_text_material_dark.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_50"/> + <item android:color="@color/system_primary_50"/> +</selector> diff --git a/core/res/res/color/text_color_primary_device_default_light.xml b/core/res/res/color/text_color_primary_device_default_light.xml new file mode 100644 index 000000000000..bdc4fa92b2f2 --- /dev/null +++ b/core/res/res/color/text_color_primary_device_default_light.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. + --> +<!-- Please see primary_text_material_light.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_900"/> + <item android:color="@color/system_primary_900"/> +</selector> diff --git a/core/res/res/color/text_color_secondary_device_default_dark.xml b/core/res/res/color/text_color_secondary_device_default_dark.xml new file mode 100644 index 000000000000..799636addd4b --- /dev/null +++ b/core/res/res/color/text_color_secondary_device_default_dark.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. + --> +<!-- Please see secondary_text_material_dark.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_200"/> + <item android:color="@color/system_primary_200"/> +</selector> diff --git a/core/res/res/color/text_color_secondary_device_default_light.xml b/core/res/res/color/text_color_secondary_device_default_light.xml new file mode 100644 index 000000000000..4793bb8e0360 --- /dev/null +++ b/core/res/res/color/text_color_secondary_device_default_light.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. + --> +<!-- Please see secondary_text_material_light.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_700"/> + <item android:color="@color/system_primary_700"/> +</selector> diff --git a/core/res/res/color/text_color_tertiary_device_default_dark.xml b/core/res/res/color/text_color_tertiary_device_default_dark.xml new file mode 100644 index 000000000000..c82863109e7d --- /dev/null +++ b/core/res/res/color/text_color_tertiary_device_default_dark.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. + --> +<!-- Please see tertiary_text_material_dark.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_400"/> + <item android:color="@color/system_primary_400"/> +</selector> diff --git a/core/res/res/color/text_color_tertiary_device_default_light.xml b/core/res/res/color/text_color_tertiary_device_default_light.xml new file mode 100644 index 000000000000..82c420ad97fc --- /dev/null +++ b/core/res/res/color/text_color_tertiary_device_default_light.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. + --> +<!-- Please see tertiary_text_material_light.xml --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="@color/system_primary_500"/> + <item android:color="@color/system_primary_500"/> +</selector> diff --git a/core/res/res/drawable/conversation_unread_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml index d3e00cfbf8b1..f95044a7fe76 100644 --- a/core/res/res/drawable/conversation_unread_bg.xml +++ b/core/res/res/drawable/expand_button_pill_bg.xml @@ -14,6 +14,6 @@ ~ limitations under the License. --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <corners android:radius="20sp" /> + <corners android:radius="@dimen/notification_expand_button_pill_height" /> <solid android:color="@android:color/white" /> </shape>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_collapse_notification.xml b/core/res/res/drawable/ic_collapse_notification.xml index ca4f0ed27a0b..a06ec9fc813f 100644 --- a/core/res/res/drawable/ic_collapse_notification.xml +++ b/core/res/res/drawable/ic_collapse_notification.xml @@ -21,8 +21,5 @@ Copyright (C) 2020 The Android Open Source Project android:viewportHeight="24.0"> <path android:fillColor="#FF000000" - android:pathData="M18.59,16.41L20.0,15.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,9.83"/> - <path - android:pathData="M0 0h24v24H0V0z" - android:fillColor="#00000000"/> + android:pathData="M18.59,15.41L20.0,14.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,8.83"/> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_expand_notification.xml b/core/res/res/drawable/ic_expand_notification.xml index a080ce43cfec..160a9c2c1970 100644 --- a/core/res/res/drawable/ic_expand_notification.xml +++ b/core/res/res/drawable/ic_expand_notification.xml @@ -21,8 +21,5 @@ Copyright (C) 2014 The Android Open Source Project android:viewportHeight="24.0"> <path android:fillColor="#FF000000" - android:pathData="M5.41,7.59L4.0,9.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,14.17"/> - <path - android:pathData="M24 24H0V0h24v24z" - android:fillColor="#00000000"/> + android:pathData="M5.41,8.59L4.0,10.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,15.17"/> </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 new file mode 100644 index 000000000000..f92e6d63cbe7 --- /dev/null +++ b/core/res/res/layout/notification_expand_button.xml @@ -0,0 +1,56 @@ +<?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.NotificationExpandButton + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|end" + android:contentDescription="@string/expand_button_content_description_collapsed" + android:padding="16dp" + android:visibility="gone" + > + + <LinearLayout + android:id="@+id/expand_button_pill" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_expand_button_pill_height" + android:orientation="horizontal" + android:background="@drawable/expand_button_pill_bg" + > + + <TextView + android:id="@+id/expand_button_number" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_expand_button_pill_height" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:gravity="center_vertical" + android:paddingLeft="8dp" + /> + + <ImageView + android:id="@+id/expand_button_icon" + android:layout_width="@dimen/notification_expand_button_pill_height" + android:layout_height="@dimen/notification_expand_button_pill_height" + android:padding="2dp" + android:scaleType="fitCenter" + android:importantForAccessibility="no" + /> + + </LinearLayout> + +</com.android.internal.widget.NotificationExpandButton> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 1de1d049197c..81a79c50c3ef 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -30,7 +30,8 @@ android:id="@+id/left_icon" android:layout_width="@dimen/notification_left_icon_size" android:layout_height="@dimen/notification_left_icon_size" - android:layout_gravity="center_vertical|start" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" android:layout_marginStart="@dimen/notification_left_icon_start" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" @@ -43,7 +44,8 @@ android:id="@+id/icon" android:layout_width="@dimen/notification_icon_circle_size" android:layout_height="@dimen/notification_icon_circle_size" - android:layout_gravity="center_vertical|start" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" android:layout_marginStart="@dimen/notification_icon_circle_start" android:background="@drawable/notification_icon_circle" android:padding="@dimen/notification_icon_circle_padding" @@ -55,10 +57,12 @@ android:id="@+id/notification_top_line" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_gravity="center_vertical" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_toStartOf="@id/expand_button" + android:layout_alignWithParentIfMissing="true" android:clipChildren="false" android:gravity="center_vertical" - android:paddingEnd="@dimen/notification_heading_margin_end" android:paddingStart="@dimen/notification_content_margin_start" android:theme="@style/Theme.DeviceDefault.Notification" > @@ -71,19 +75,15 @@ android:id="@+id/alternate_expand_target" android:layout_width="@dimen/notification_content_margin_start" android:layout_height="match_parent" - android:layout_gravity="start" + android:layout_alignParentStart="true" android:importantForAccessibility="no" /> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/expand_button_content_description_collapsed" - android:paddingTop="@dimen/notification_expand_button_padding_top" - android:scaleType="center" - android:visibility="gone" + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" /> </NotificationHeaderView> diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index b83611bcc177..bad9a6ba6184 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -74,14 +74,10 @@ android:layout_height="match_parent" android:layout_gravity="end"> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" - android:contentDescription="@string/expand_button_content_description_collapsed" - android:paddingTop="@dimen/notification_expand_button_padding_top" - android:scaleType="center" /> </FrameLayout> diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml index 471d874c59f5..7b52ec30abe6 100644 --- a/core/res/res/layout/notification_template_material_call.xml +++ b/core/res/res/layout/notification_template_material_call.xml @@ -72,15 +72,10 @@ </LinearLayout> <!-- TODO(b/179178086): remove padding from main column when this is visible --> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="top|end" - android:contentDescription="@string/expand_button_content_description_collapsed" - android:paddingTop="@dimen/notification_expand_button_padding_top" - android:scaleType="center" - android:visibility="gone" /> </LinearLayout> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index f3aa54066c92..42fb4a26dd3b 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -104,11 +104,10 @@ <LinearLayout android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" - android:layout_height="@dimen/conversation_expand_button_size" - android:paddingStart="@dimen/conversation_expand_button_side_margin" + android:layout_height="@dimen/conversation_expand_button_height" android:orientation="horizontal" android:layout_gravity="end|top" - android:paddingEnd="@dimen/conversation_expand_button_side_margin" + android:paddingEnd="0dp" android:clipToPadding="false" android:clipChildren="false" > @@ -118,34 +117,16 @@ android:forceHasOverlappingRendering="false" android:layout_width="40dp" android:layout_height="40dp" - android:layout_marginEnd="11dp" + android:layout_marginStart="@dimen/conversation_image_start_margin" android:spacing="0dp" android:layout_gravity="center" android:clipToPadding="false" android:clipChildren="false" /> - <!-- Unread Count --> - <TextView - android:id="@+id/conversation_unread_count" - android:layout_width="33sp" - android:layout_height="wrap_content" - android:layout_marginEnd="11dp" - android:layout_gravity="center" - android:gravity="center" - android:padding="2dp" - android:visibility="gone" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification" - android:textColor="#FFFFFF" - android:textSize="12sp" - android:background="@drawable/conversation_unread_bg" - /> - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:drawable="@drawable/ic_expand_notification" - android:contentDescription="@string/expand_button_content_description_collapsed" /> </LinearLayout> </FrameLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 100983bcef0d..efc8fe9aafa2 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8424,6 +8424,9 @@ <!-- Fully qualified class name of an activity that allows the user to modify the settings for this service. --> <attr name="settingsActivity" /> + <!-- Fully qualified class name of an activity that allows the user to view any passwords + saved by this service. --> + <attr name="passwordsActivity" format="string" /> <!-- Specifies whether the AutofillService supports inline suggestions--> <attr name="supportsInlineSuggestions" format="boolean" /> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 45e11ba9820e..bed5c31bd327 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1773,6 +1773,8 @@ <enum name="maps" value="6" /> <!-- Apps which are primarily productivity apps, such as cloud storage or workplace apps. --> <enum name="productivity" value="7" /> + <!-- Apps which are primarily accessibility apps, such as screen-readers. --> + <enum name="accessibility" value="8" /> </attr> <!-- Declares the kind of classloader this application's classes must be loaded with --> diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml index 1020c14ef665..9b5632174e44 100644 --- a/core/res/res/values/colors_device_defaults.xml +++ b/core/res/res/values/colors_device_defaults.xml @@ -42,12 +42,7 @@ <color name="background_floating_device_default_dark">@color/system_primary_900</color> <color name="background_floating_device_default_light">@color/system_primary_100</color> - <color name="text_color_primary_device_default_light">@color/system_primary_900</color> - <color name="text_color_primary_device_default_dark">@color/system_primary_50</color> - <color name="text_color_secondary_device_default_light">@color/system_primary_700</color> - <color name="text_color_secondary_device_default_dark">@color/system_primary_200</color> - <color name="text_color_tertiary_device_default_light">@color/system_primary_500</color> - <color name="text_color_tertiary_device_default_dark">@color/system_primary_400</color> + <!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors--> <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color> <color name="foreground_device_default_dark">@color/text_color_primary_device_default_dark</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 12cb3980f785..f38264c41211 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -848,6 +848,15 @@ <!-- y-intercept --> <item>1.000000000000000</item> </string-array> + <!-- Default strength, in percentage, of bright color reduction when activated. --> + <integer name="config_reduceBrightColorsStrengthDefault">0</integer> + + <!-- Minimum strength, in percentage, supported by bright color reduction. --> + <integer name="config_reduceBrightColorsStrengthMin">0</integer> + + <!-- Maximum strength, in percentage, supported by bright color reduction. --> + <integer name="config_reduceBrightColorsStrengthMax">100</integer> + <!-- Boolean indicating whether display white balance is supported. --> <bool name="config_displayWhiteBalanceAvailable">false</bool> @@ -1783,7 +1792,7 @@ * SDK level 28 makes the following algorithms mandatory : "cbc(aes)", "hmac(md5)", "hmac(sha1)", "hmac(sha256)", "hmac(sha384)", "hmac(sha512)", "rfc4106(gcm(aes))" * SDK level 31 makes the following algorithms mandatory : "rfc3686(ctr(aes))", - "xcbc(aes)", "rfc7539esp(chacha20,poly1305)" + "xcbc(aes)", "cmac(aes)", "rfc7539esp(chacha20,poly1305)" --> <string-array name="config_optionalIpSecAlgorithms" translatable="false"> <!-- Add algorithm here --> @@ -1955,6 +1964,8 @@ <string name="config_systemContacts" translatable="false">com.android.contacts</string> <!-- The name of the package that will hold the speech recognizer role by default. --> <string name="config_systemSpeechRecognizer" translatable="false"></string> + <!-- The name of the package that will hold the system Wi-Fi coex manager role. --> + <string name="config_systemWifiCoexManager" translateable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> @@ -3939,6 +3950,10 @@ color supplied by the Notification.Builder if present. --> <bool name="config_tintNotificationActionButtons">true</bool> + <!-- Flag indicating that tinted items (actions, expander, etc) are to be tinted using the + theme color, rather than the notification color. --> + <bool name="config_tintNotificationsWithTheme">true</bool> + <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app --> <bool name="config_showAreaUpdateInfoSettings">false</bool> @@ -4653,20 +4668,11 @@ <!-- WindowsManager JetPack display features --> <string name="config_display_features" translatable="false" /> - <!-- Physical Display IDs of the display-devices that are swapped when a folding device folds. - This list is expected to contain two elements: the first is the display to use - when the device is folded, the second is the display to use when unfolded. If the array - is empty or the display IDs are not recognized, this feature is turned off and the value - ignored. - TODO: b/170470621 - remove once we can have multiple Internal displays in DMS as - well as a notification from DisplayStateManager. --> - <string-array name="config_internalFoldedPhysicalDisplayIds" translatable="false" /> - - <!-- Aspect ratio of task level letterboxing. Values <= 1.0 will be ignored. - Note: Activity min/max aspect ratio restrictions will still be respected by the - activity-level letterboxing (size-compat mode). Therefore this override can control the - maximum screen area that can be occupied by the app in the letterbox mode. --> - <item name="config_taskLetterboxAspectRatio" format="float" type="dimen">0.0</item> + <!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored. + Note: Activity min/max aspect ratio restrictions will still be respected. + Therefore this override can control the maximum screen area that can be occupied by + the app in the letterbox mode. --> + <item name="config_fixedOrientationLetterboxAspectRatio" format="float" type="dimen">0.0</item> <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and corners of the activity won't be rounded. --> @@ -4692,6 +4698,14 @@ <!-- If true, hide the display cutout with display area --> <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool> + <!-- The timeout value in milliseconds used by SelectionActionModeHelper for each selections + when TextClassifier has been initialized. --> + <integer name="config_smartSelectionInitializedTimeoutMillis">200</integer> + + <!-- The timeout value in milliseconds used by SelectionActionModeHelper for each selections + when TextClassifier has not been initialized. --> + <integer name="config_smartSelectionInitializingTimeoutMillis">500</integer> + <!-- Indicates that default fitness tracker app needs to request sensor and location permissions. --> <bool name="config_trackerAppNeedsPermissions">false</bool> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index c2b6b99dcc1c..10aa7b3f4354 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -224,7 +224,7 @@ <dimen name="notification_content_margin_end">16dp</dimen> <!-- The margin on the end of the top-line content views (accommodates the expander) --> - <dimen name="notification_heading_margin_end">48dp</dimen> + <dimen name="notification_heading_margin_end">56dp</dimen> <!-- The margin for text at the end of the image view for media notifications --> <dimen name="notification_media_image_margin_end">72dp</dimen> @@ -248,7 +248,7 @@ <dimen name="call_notification_collapsible_indent">64dp</dimen> <!-- The size of icons for visual actions in the notification_material_action_list --> - <dimen name="notification_actions_icon_size">48dp</dimen> + <dimen name="notification_actions_icon_size">56dp</dimen> <!-- The size of icons for visual actions in the notification_material_action_list --> <dimen name="notification_actions_icon_drawable_size">20dp</dimen> @@ -314,10 +314,10 @@ <dimen name="notification_conversation_header_separating_margin">4dp</dimen> <!-- The absolute size of the notification expand icon. --> - <dimen name="notification_header_expand_icon_size">48dp</dimen> + <dimen name="notification_header_expand_icon_size">56dp</dimen> - <!-- The top padding for the notification expand button. --> - <dimen name="notification_expand_button_padding_top">1dp</dimen> + <!-- the height of the expand button pill --> + <dimen name="notification_expand_button_pill_height">24dp</dimen> <!-- Vertical margin for the headerless notification content, when content has 1 line --> <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) --> @@ -690,6 +690,13 @@ <!-- The default minimal size of a PiP task, in both dimensions. --> <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen> + <!-- + The overridable minimal size of a PiP task, in both dimensions. + Different from default_minimal_size_pip_resizable_task, this is to limit the dimension + when the pinned stack size is overridden by app via minWidth/minHeight. + --> + <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen> + <!-- Height of a task when in minimized mode from the top when launcher is resizable. --> <dimen name="task_height_of_minimized_mode">80dp</dimen> @@ -739,7 +746,7 @@ <dimen name="notification_right_icon_headerless_margin">20dp</dimen> <!-- The top margin of the right icon in the "big" notification states --> <!-- TODO(b/181048615): Move the large icon below the expander in big states --> - <dimen name="notification_right_icon_big_margin_top">16dp</dimen> + <dimen name="notification_right_icon_big_margin_top">20dp</dimen> <!-- The size of the left icon --> <dimen name="notification_left_icon_size">@dimen/notification_icon_circle_size</dimen> <!-- The left padding of the left icon --> @@ -770,13 +777,10 @@ <dimen name="conversation_icon_circle_start">28dp</dimen> <!-- Start of the content in the conversation template --> <dimen name="conversation_content_start">80dp</dimen> - <!-- Size of the expand button in the conversation layout --> - <dimen name="conversation_expand_button_size">80dp</dimen> - <!-- Top margin of the expand button for conversations when expanded --> - <dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen> - <!-- Side margin of the expand button for conversations. - width of expand asset (22) + 2 * this (13) == notification_header_expand_icon_size (48) --> - <dimen name="conversation_expand_button_side_margin">13dp</dimen> + <!-- Height of the expand button in the conversation layout --> + <dimen name="conversation_expand_button_height">80dp</dimen> + <!-- this is the margin between the Conversation image and the content --> + <dimen name="conversation_image_start_margin">12dp</dimen> <!-- Side margins of the conversation badge in relation to the conversation icon --> <dimen name="conversation_badge_side_margin">36dp</dimen> <!-- size of the notification badge when applied to the conversation icon --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 2004d0a8d15c..7702ee437c43 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3085,6 +3085,7 @@ <public name="hand_secondTint"/> <public name="hand_secondTintMode"/> <public name="dataExtractionRules"/> + <public name="passwordsActivity"/> </public-group> <public-group type="drawable" first-id="0x010800b5"> @@ -3168,6 +3169,8 @@ <public name="config_customMediaSessionPolicyProvider" /> <!-- @hide @SystemApi --> <public name="config_systemSpeechRecognizer" /> + <!-- @hide @SystemApi --> + <public name="config_systemWifiCoexManager" /> </public-group> <public-group type="id" first-id="0x01020055"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 71ba44b0ded1..2b1168f14f21 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1524,8 +1524,15 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_mediaLocation">Allows the app to read locations from your media collection.</string> + <!-- Name for an app setting that lets the user authenticate for that app using biometrics (e.g. fingerprint or face). [CHAR LIMIT=30] --> + <string name="biometric_app_setting_name">Use biometrics</string> + <!-- Name for an app setting that lets the user authenticate for that app using biometrics (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="biometric_or_screen_lock_app_setting_name">Use biometrics or screen lock</string> <!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] --> <string name="biometric_dialog_default_title">Verify it\u2019s you</string> + <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] --> + <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string> + <!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] --> <string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string> <!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] --> @@ -1539,6 +1546,11 @@ <!-- Message returned to applications when an unexpected/unknown error occurs. [CHAR LIMIT=50]--> <string name="biometric_error_generic">Error authenticating</string> + <!-- Name for an app setting that lets the user authenticate for that app with their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=30] --> + <string name="screen_lock_app_setting_name">Use screen lock</string> + <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="screen_lock_dialog_default_subtitle">Enter your device credential to continue</string> + <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized --> <string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string> <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized --> @@ -1585,6 +1597,11 @@ <!-- Template to be used to name enrolled fingerprints by default. --> <string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string> + + <!-- Name for an app setting that lets the user authenticate for that app with their fingerprint. [CHAR LIMIT=30] --> + <string name="fingerprint_app_setting_name">Use fingerprint</string> + <!-- Name for an app setting that lets the user authenticate for that app with their fingerprint or screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="fingerprint_or_screen_lock_app_setting_name">Use fingerprint or screen lock</string> <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with their fingerprint. [CHAR LIMIT=70] --> <string name="fingerprint_dialog_default_subtitle">Use your fingerprint to continue</string> @@ -1681,6 +1698,13 @@ <!-- Template to be used to name enrolled faces by default. [CHAR LIMIT=10] --> <string name="face_name_template">Face <xliff:g id="faceId" example="1">%d</xliff:g></string> + <!-- Name for an app setting that lets the user authenticate for that app with their face. [CHAR LIMIT=30] --> + <string name="face_app_setting_name">Use face unlock</string> + <!-- Name for an app setting that lets the user authenticate for that app with their face or screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] --> + <string name="face_or_screen_lock_app_setting_name">Use face or screen lock</string> + <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with their face. [CHAR LIMIT=70] --> + <string name="face_dialog_default_subtitle">Use face unlock to continue</string> + <!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings --> <string-array name="face_error_vendor"> </string-array> @@ -5193,6 +5217,8 @@ <string name="app_category_maps">Maps & Navigation</string> <!-- Category title for apps which are primarily productivity apps, such as cloud storage or workplace apps. [CHAR LIMIT=32] --> <string name="app_category_productivity">Productivity</string> + <!-- Category title for apps which are primarily accessibility apps, such as screen-readers. [CHAR LIMIT=32] --> + <string name="app_category_accessibility">Accessibility</string> <!-- Channel name for DeviceStorageMonitor notifications --> <string name="device_storage_monitor_notification_channel">Device storage</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index dbb584dfe293..2fff7b5f6298 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -482,6 +482,8 @@ <java-symbol type="array" name="config_integrityRuleProviderPackages" /> <java-symbol type="bool" name="config_useAssistantVolume" /> <java-symbol type="string" name="config_bandwidthEstimateSource" /> + <java-symbol type="integer" name="config_smartSelectionInitializedTimeoutMillis" /> + <java-symbol type="integer" name="config_smartSelectionInitializingTimeoutMillis" /> <java-symbol type="color" name="tab_indicator_text_v4" /> @@ -1893,6 +1895,7 @@ <java-symbol type="bool" name="config_notificationHeaderClickableForExpand" /> <java-symbol type="bool" name="config_enableNightMode" /> <java-symbol type="bool" name="config_tintNotificationActionButtons" /> + <java-symbol type="bool" name="config_tintNotificationsWithTheme" /> <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" /> <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> @@ -1948,6 +1951,7 @@ <java-symbol type="fraction" name="config_dimBehindFadeDuration" /> <java-symbol type="dimen" name="default_minimal_size_resizable_task" /> <java-symbol type="dimen" name="default_minimal_size_pip_resizable_task" /> + <java-symbol type="dimen" name="overridable_minimal_size_pip_resizable_task" /> <java-symbol type="dimen" name="task_height_of_minimized_mode" /> <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" /> <java-symbol type="bool" name="config_allowPriorityVibrationsInLowPowerMode" /> @@ -2468,7 +2472,10 @@ <java-symbol type="string" name="config_keyguardComponent" /> <!-- Biometric messages --> + <java-symbol type="string" name="biometric_app_setting_name" /> + <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" /> <java-symbol type="string" name="biometric_dialog_default_title" /> + <java-symbol type="string" name="biometric_dialog_default_subtitle" /> <java-symbol type="string" name="biometric_error_hw_unavailable" /> <java-symbol type="string" name="biometric_error_user_canceled" /> <java-symbol type="string" name="biometric_not_recognized" /> @@ -2476,6 +2483,10 @@ <java-symbol type="string" name="biometric_error_device_not_secured" /> <java-symbol type="string" name="biometric_error_generic" /> + <!-- Device credential strings for BiometricManager --> + <java-symbol type="string" name="screen_lock_app_setting_name" /> + <java-symbol type="string" name="screen_lock_dialog_default_subtitle" /> + <!-- Fingerprint messages --> <java-symbol type="string" name="fingerprint_error_unable_to_process" /> <java-symbol type="string" name="fingerprint_error_hw_not_available" /> @@ -2493,6 +2504,8 @@ <java-symbol type="string" name="fingerprint_error_lockout" /> <java-symbol type="string" name="fingerprint_error_lockout_permanent" /> <java-symbol type="string" name="fingerprint_name_template" /> + <java-symbol type="string" name="fingerprint_app_setting_name" /> + <java-symbol type="string" name="fingerprint_or_screen_lock_app_setting_name" /> <java-symbol type="string" name="fingerprint_dialog_default_subtitle" /> <java-symbol type="string" name="fingerprint_authenticated" /> <java-symbol type="string" name="fingerprint_error_no_fingerprints" /> @@ -2540,6 +2553,9 @@ <java-symbol type="string" name="face_acquired_sensor_dirty" /> <java-symbol type="array" name="face_acquired_vendor" /> <java-symbol type="string" name="face_name_template" /> + <java-symbol type="string" name="face_app_setting_name" /> + <java-symbol type="string" name="face_or_screen_lock_app_setting_name" /> + <java-symbol type="string" name="face_dialog_default_subtitle" /> <java-symbol type="string" name="face_authenticated_no_confirmation_required" /> <java-symbol type="string" name="face_authenticated_confirmation_required" /> <java-symbol type="string" name="face_error_security_update_required" /> @@ -2892,6 +2908,9 @@ <java-symbol type="id" name="header_text" /> <java-symbol type="id" name="header_text_secondary" /> <java-symbol type="id" name="expand_button" /> + <java-symbol type="id" name="expand_button_pill" /> + <java-symbol type="id" name="expand_button_number" /> + <java-symbol type="id" name="expand_button_icon" /> <java-symbol type="id" name="alternate_expand_target" /> <java-symbol type="id" name="notification_header" /> <java-symbol type="id" name="notification_top_line" /> @@ -2912,7 +2931,6 @@ <java-symbol type="dimen" name="notification_header_background_height" /> <java-symbol type="dimen" name="notification_header_touchable_height" /> <java-symbol type="dimen" name="notification_header_expand_icon_size" /> - <java-symbol type="dimen" name="notification_expand_button_padding_top" /> <java-symbol type="dimen" name="notification_header_icon_size" /> <java-symbol type="dimen" name="notification_header_app_name_margin_start" /> <java-symbol type="dimen" name="notification_header_separating_margin" /> @@ -3224,6 +3242,9 @@ <java-symbol type="bool" name="config_reduceBrightColorsAvailable" /> <java-symbol type="array" name="config_reduceBrightColorsCoefficients" /> <java-symbol type="array" name="config_reduceBrightColorsCoefficientsNonlinear" /> + <java-symbol type="integer" name="config_reduceBrightColorsStrengthDefault" /> + <java-symbol type="integer" name="config_reduceBrightColorsStrengthMin" /> + <java-symbol type="integer" name="config_reduceBrightColorsStrengthMax" /> <java-symbol type="array" name="config_availableColorModes" /> <java-symbol type="array" name="config_mappedColorModes" /> <java-symbol type="string" name="config_vendorColorModesRestoreHint" /> @@ -3293,6 +3314,7 @@ <java-symbol type="string" name="app_category_news" /> <java-symbol type="string" name="app_category_maps" /> <java-symbol type="string" name="app_category_productivity" /> + <java-symbol type="string" name="app_category_accessibility" /> <java-symbol type="raw" name="fallback_categories" /> @@ -4029,7 +4051,6 @@ <java-symbol type="id" name="message_icon_container" /> <java-symbol type="id" name="conversation_image_message_container" /> <java-symbol type="id" name="conversation_icon_container" /> - <java-symbol type="dimen" name="conversation_expand_button_top_margin_expanded" /> <java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" /> <java-symbol type="dimen" name="conversation_badge_side_margin" /> <java-symbol type="dimen" name="conversation_avatar_size" /> @@ -4050,7 +4071,6 @@ <java-symbol type="dimen" name="button_padding_horizontal_material" /> <java-symbol type="dimen" name="button_inset_horizontal_material" /> <java-symbol type="layout" name="conversation_face_pile_layout" /> - <java-symbol type="id" name="conversation_unread_count" /> <java-symbol type="string" name="unread_convo_overflow" /> <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" /> <java-symbol type="drawable" name="conversation_badge_background" /> @@ -4157,12 +4177,11 @@ <java-symbol type="dimen" name="default_background_blur_radius" /> <java-symbol type="array" name="config_keep_warming_services" /> <java-symbol type="string" name="config_display_features" /> - <java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" /> <java-symbol type="dimen" name="controls_thumbnail_image_max_height" /> <java-symbol type="dimen" name="controls_thumbnail_image_max_width" /> - <java-symbol type="dimen" name="config_taskLetterboxAspectRatio" /> + <java-symbol type="dimen" name="config_fixedOrientationLetterboxAspectRatio" /> <java-symbol type="integer" name="config_letterboxActivityCornersRadius" /> <java-symbol type="integer" name="config_letterboxBackgroundType" /> <java-symbol type="color" name="config_letterboxBackgroundColor" /> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java index 3706e4b3d8e8..b0c1f25ad030 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; +import android.net.TetheringManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; @@ -141,7 +142,7 @@ public class ConnectivityManagerTestBase extends InstrumentationTestCase { mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - mIntentFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); + mIntentFilter.addAction(TetheringManager.ACTION_TETHER_STATE_CHANGED); mContext.registerReceiver(mWifiReceiver, mIntentFilter); logv("Clear Wifi before we start the test."); diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java index 0fe44630f308..9e8837386128 100644 --- a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java +++ b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java @@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat; import android.os.Bundle; +import com.google.common.collect.ImmutableList; + import org.junit.Test; import java.util.List; @@ -67,9 +69,9 @@ public class SearchSpecTest { SearchSpec searchSpec = new SearchSpec.Builder() .setTermMatch(SearchSpec.TERM_MATCH_PREFIX) - .addProjection("TypeA", "field1", "field2.subfield2") - .addProjection("TypeB", "field7") - .addProjection("TypeC") + .addProjection("TypeA", ImmutableList.of("field1", "field2.subfield2")) + .addProjection("TypeB", ImmutableList.of("field7")) + .addProjection("TypeC", ImmutableList.of()) .build(); Map<String, List<String>> typePropertyPathMap = searchSpec.getProjections(); diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java index bf6b07f71839..677f4bd9e46d 100644 --- a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java +++ b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java @@ -42,13 +42,13 @@ public class SetSchemaRequestTest { } @Test - public void testInvalidSchemaReferences_fromSystemUiVisibility() { + public void testInvalidSchemaReferences_fromDisplayedBySystem() { IllegalArgumentException expected = expectThrows( IllegalArgumentException.class, () -> new SetSchemaRequest.Builder() - .setSchemaTypeVisibilityForSystemUi("InvalidSchema", false) + .setSchemaTypeDisplayedBySystem("InvalidSchema", false) .build()); assertThat(expected).hasMessageThat().contains("referenced, but were not added"); } @@ -71,30 +71,30 @@ public class SetSchemaRequestTest { } @Test - public void testSchemaTypeVisibilityForSystemUi_visible() { + public void testSetSchemaTypeDisplayedBySystem_displayed() { AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build(); - // By default, the schema is visible. + // By default, the schema is displayed. SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build(); - assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty(); + assertThat(request.getSchemasNotDisplayedBySystem()).isEmpty(); request = new SetSchemaRequest.Builder() .addSchemas(schema) - .setSchemaTypeVisibilityForSystemUi("Schema", true) + .setSchemaTypeDisplayedBySystem("Schema", true) .build(); - assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty(); + assertThat(request.getSchemasNotDisplayedBySystem()).isEmpty(); } @Test - public void testSchemaTypeVisibilityForSystemUi_notVisible() { + public void testSetSchemaTypeDisplayedBySystem_notDisplayed() { AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build(); SetSchemaRequest request = new SetSchemaRequest.Builder() .addSchemas(schema) - .setSchemaTypeVisibilityForSystemUi("Schema", false) + .setSchemaTypeDisplayedBySystem("Schema", false) .build(); - assertThat(request.getSchemasNotVisibleToSystemUi()).containsExactly("Schema"); + assertThat(request.getSchemasNotDisplayedBySystem()).containsExactly("Schema"); } @Test diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 50b52eb2a0ea..6c8b94129c82 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -148,13 +148,15 @@ public class ObjectPoolTests { PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putInt("k", 4); IBinder assistToken = new Binder(); + IBinder shareableActivityToken = new Binder(); Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder() .setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config()) .setOverrideConfig(overrideConfig).setCompatInfo(compat).setReferrer(referrer) .setProcState(procState).setState(bundle).setPersistentState(persistableBundle) .setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList()) - .setIsForward(true).setAssistToken(assistToken).build(); + .setIsForward(true).setAssistToken(assistToken) + .setShareableActivityToken(shareableActivityToken).build(); LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build(); LaunchActivityItem item = itemSupplier.get(); diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java index 02e75dd76a07..1a06789e729f 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java +++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java @@ -109,6 +109,7 @@ class TestUtils { private boolean mIsForward; private ProfilerInfo mProfilerInfo; private IBinder mAssistToken; + private IBinder mShareableActivityToken; private FixedRotationAdjustments mFixedRotationAdjustments; LaunchActivityItemBuilder setIntent(Intent intent) { @@ -196,6 +197,11 @@ class TestUtils { return this; } + LaunchActivityItemBuilder setShareableActivityToken(IBinder shareableActivityToken) { + mShareableActivityToken = shareableActivityToken; + return this; + } + LaunchActivityItemBuilder setFixedRotationAdjustments(FixedRotationAdjustments fra) { mFixedRotationAdjustments = fra; return this; @@ -206,7 +212,8 @@ class TestUtils { mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo, mAssistToken, - null /* activityClientController */, mFixedRotationAdjustments); + null /* activityClientController */, mFixedRotationAdjustments, + mShareableActivityToken); } } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index f6d985b1788f..6f3d7ae5eb3c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -207,6 +207,7 @@ public class TransactionParcelTests { .setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic()) .setPendingNewIntents(referrerIntentList()).setIsForward(true) .setAssistToken(new Binder()).setFixedRotationAdjustments(fixedRotationAdjustments) + .setShareableActivityToken(new Binder()) .build(); writeAndPrepareForReading(item); diff --git a/core/tests/coretests/src/android/app/usage/OWNERS b/core/tests/coretests/src/android/app/usage/OWNERS new file mode 100644 index 000000000000..1271fa799808 --- /dev/null +++ b/core/tests/coretests/src/android/app/usage/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 532296 +include /services/usage/OWNERS diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java index 4d04a7af4693..8de9454ddeda 100644 --- a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java +++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java @@ -78,15 +78,15 @@ public class UsageStatsPersistenceTest { "VALID_FLAG_BITS", "UNASSIGNED_TOKEN", "MAX_EVENT_TYPE"}; // All fields in this list are final constants defining event types and not persisted private static final String[] EVENT_TYPES = {"NONE", "ACTIVITY_DESTROYED", "ACTIVITY_PAUSED", - "ACTIVITY_RESUMED", "ACTIVITY_STOPPED", "CHOOSER_ACTION", "CONFIGURATION_CHANGE", - "CONTINUE_PREVIOUS_DAY", "CONTINUING_FOREGROUND_SERVICE", "DEVICE_SHUTDOWN", - "DEVICE_STARTUP", "END_OF_DAY", "FLUSH_TO_DISK", "FOREGROUND_SERVICE_START", - "FOREGROUND_SERVICE_STOP", "KEYGUARD_HIDDEN", "KEYGUARD_SHOWN", "LOCUS_ID_SET", - "MOVE_TO_BACKGROUND", "MOVE_TO_FOREGROUND", "NOTIFICATION_INTERRUPTION", - "NOTIFICATION_SEEN", "ROLLOVER_FOREGROUND_SERVICE", "SCREEN_INTERACTIVE", - "SCREEN_NON_INTERACTIVE", "SHORTCUT_INVOCATION", "SLICE_PINNED", "SLICE_PINNED_PRIV", - "STANDBY_BUCKET_CHANGED", "SYSTEM_INTERACTION", "USER_INTERACTION", "USER_STOPPED", - "USER_UNLOCKED"}; + "ACTIVITY_RESUMED", "ACTIVITY_STOPPED", "APP_COMPONENT_USED", "CHOOSER_ACTION", + "CONFIGURATION_CHANGE", "CONTINUE_PREVIOUS_DAY", "CONTINUING_FOREGROUND_SERVICE", + "DEVICE_SHUTDOWN", "DEVICE_STARTUP", "END_OF_DAY", "FLUSH_TO_DISK", + "FOREGROUND_SERVICE_START", "FOREGROUND_SERVICE_STOP", "KEYGUARD_HIDDEN", + "KEYGUARD_SHOWN", "LOCUS_ID_SET", "MOVE_TO_BACKGROUND", "MOVE_TO_FOREGROUND", + "NOTIFICATION_INTERRUPTION", "NOTIFICATION_SEEN", "ROLLOVER_FOREGROUND_SERVICE", + "SCREEN_INTERACTIVE", "SCREEN_NON_INTERACTIVE", "SHORTCUT_INVOCATION", "SLICE_PINNED", + "SLICE_PINNED_PRIV", "STANDBY_BUCKET_CHANGED", "SYSTEM_INTERACTION", "USER_INTERACTION", + "USER_STOPPED", "USER_UNLOCKED"}; @Test public void testUsageEventsFields() { diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index eae41e37e5f3..7bc81cd2f928 100644 --- a/core/tests/coretests/src/android/graphics/FontListParserTest.java +++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java @@ -26,6 +26,8 @@ import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static junit.framework.Assert.fail; + import android.graphics.fonts.FontStyle; import android.os.LocaleList; import android.text.FontConfig; @@ -44,6 +46,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -221,9 +224,113 @@ public final class FontListParserTest { .that(readFamily(serialized)).isEqualTo(expected); } + @Test + public void invalidXml_unpaired_family() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc</font>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unpaired_font() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unpaired_axis() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc" + + " <axis tag=\"wght\" styleValue=\"0\" >" + + " </font>" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unclosed_family() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'" + + " <font index='0'>test.ttc</font>" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unclosed_font() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + + @Test + public void invalidXml_unclosed_axis() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font index='0'>test.ttc" + + " <axis tag=\"wght\" styleValue=\"0\"" + + " </font>" + + " </family>" + + "</familyset>"; + + try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + FontListParser.parse(is); + fail(); + } catch (IOException | XmlPullParserException e) { + // pass + } + } + private FontConfig.FontFamily readFamily(String xml) throws IOException, XmlPullParserException { - StandardCharsets.UTF_8.name(); ByteArrayInputStream buffer = new ByteArrayInputStream( xml.getBytes(StandardCharsets.UTF_8)); XmlPullParser parser = Xml.newPullParser(); diff --git a/core/tests/coretests/src/android/os/BytesMatcherTest.java b/core/tests/coretests/src/android/os/BytesMatcherTest.java new file mode 100644 index 000000000000..67c1b3c947fb --- /dev/null +++ b/core/tests/coretests/src/android/os/BytesMatcherTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import static com.android.internal.util.HexDump.hexStringToByteArray; + +import android.bluetooth.BluetoothUuid; +import android.net.MacAddress; + +import androidx.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SmallTest +public class BytesMatcherTest extends TestCase { + @Test + public void testEmpty() throws Exception { + BytesMatcher matcher = BytesMatcher.decode(""); + assertFalse(matcher.test(hexStringToByteArray("cafe"))); + assertFalse(matcher.test(hexStringToByteArray(""))); + } + + @Test + public void testExact() throws Exception { + BytesMatcher matcher = BytesMatcher.decode("+cafe"); + assertTrue(matcher.test(hexStringToByteArray("cafe"))); + assertFalse(matcher.test(hexStringToByteArray("beef"))); + assertFalse(matcher.test(hexStringToByteArray("ca"))); + assertFalse(matcher.test(hexStringToByteArray("cafe00"))); + } + + @Test + public void testMask() throws Exception { + BytesMatcher matcher = BytesMatcher.decode("+cafe/ff00"); + assertTrue(matcher.test(hexStringToByteArray("cafe"))); + assertTrue(matcher.test(hexStringToByteArray("ca88"))); + assertFalse(matcher.test(hexStringToByteArray("beef"))); + assertFalse(matcher.test(hexStringToByteArray("ca"))); + assertFalse(matcher.test(hexStringToByteArray("cafe00"))); + } + + @Test + public void testMacAddress() throws Exception { + BytesMatcher matcher = BytesMatcher.decode("+cafe00112233/ffffff000000"); + assertTrue(matcher.testMacAddress( + MacAddress.fromString("ca:fe:00:00:00:00"))); + assertFalse(matcher.testMacAddress( + MacAddress.fromString("f0:0d:00:00:00:00"))); + } + + @Test + public void testBluetoothUuid() throws Exception { + BytesMatcher matcher = BytesMatcher.decode("+cafe/ff00"); + assertTrue(matcher.testBluetoothUuid( + BluetoothUuid.parseUuidFrom(hexStringToByteArray("cafe")))); + assertFalse(matcher.testBluetoothUuid( + BluetoothUuid.parseUuidFrom(hexStringToByteArray("beef")))); + } + + /** + * Verify that single matcher can be configured to match Bluetooth UUIDs of + * varying lengths. + */ + @Test + public void testBluetoothUuid_Mixed() throws Exception { + BytesMatcher matcher = BytesMatcher.decode("+aaaa/ff00,+bbbbbbbb/ffff0000"); + assertTrue(matcher.testBluetoothUuid( + BluetoothUuid.parseUuidFrom(hexStringToByteArray("aaaa")))); + assertFalse(matcher.testBluetoothUuid( + BluetoothUuid.parseUuidFrom(hexStringToByteArray("bbbb")))); + assertTrue(matcher.testBluetoothUuid( + BluetoothUuid.parseUuidFrom(hexStringToByteArray("bbbbbbbb")))); + assertFalse(matcher.testBluetoothUuid( + BluetoothUuid.parseUuidFrom(hexStringToByteArray("aaaaaaaa")))); + } + + @Test + public void testSerialize() throws Exception { + BytesMatcher matcher = new BytesMatcher(); + matcher.addRejectRule(hexStringToByteArray("cafe00112233"), + hexStringToByteArray("ffffff000000")); + matcher.addRejectRule(hexStringToByteArray("beef00112233"), + null); + matcher.addAcceptRule(hexStringToByteArray("000000000000"), + hexStringToByteArray("000000000000")); + + assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff"))); + assertFalse(matcher.test(hexStringToByteArray("beef00112233"))); + assertTrue(matcher.test(hexStringToByteArray("beef00ffffff"))); + + // Bounce through serialization pass and confirm it still works + matcher = BytesMatcher.decode(BytesMatcher.encode(matcher)); + + assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff"))); + assertFalse(matcher.test(hexStringToByteArray("beef00112233"))); + assertTrue(matcher.test(hexStringToByteArray("beef00ffffff"))); + } + + @Test + public void testOrdering_RejectFirst() throws Exception { + BytesMatcher matcher = BytesMatcher.decode("-ff/0f,+ff/f0"); + assertFalse(matcher.test(hexStringToByteArray("ff"))); + assertTrue(matcher.test(hexStringToByteArray("f0"))); + assertFalse(matcher.test(hexStringToByteArray("0f"))); + } + + @Test + public void testOrdering_AcceptFirst() throws Exception { + BytesMatcher matcher = BytesMatcher.decode("+ff/f0,-ff/0f"); + assertTrue(matcher.test(hexStringToByteArray("ff"))); + assertTrue(matcher.test(hexStringToByteArray("f0"))); + assertFalse(matcher.test(hexStringToByteArray("0f"))); + } +} diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java index 89411902bb6b..c06405affc1b 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -20,6 +20,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import android.hardware.vibrator.IVibrator; import android.platform.test.annotations.Presubmit; import org.junit.Test; @@ -33,16 +34,16 @@ public class VibratorInfoTest { @Test public void testHasAmplitudeControl() { assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl()); - assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS - | VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL).hasAmplitudeControl()); + assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS + | IVibrator.CAP_AMPLITUDE_CONTROL).hasAmplitudeControl()); } @Test public void testHasCapabilities() { - assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS) - .hasCapability(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)); - assertFalse(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS) - .hasCapability(VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL)); + assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS) + .hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)); + assertFalse(createInfo(IVibrator.CAP_COMPOSE_EFFECTS) + .hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)); } @Test @@ -59,7 +60,7 @@ public class VibratorInfoTest { @Test public void testIsPrimitiveSupported() { - VibratorInfo info = new VibratorInfo(/* id= */ 0, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS, + VibratorInfo info = new VibratorInfo(/* id= */ 0, IVibrator.CAP_COMPOSE_EFFECTS, null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); @@ -73,30 +74,30 @@ public class VibratorInfoTest { @Test public void testEquals() { VibratorInfo empty = new VibratorInfo(1, 0, null, null); - VibratorInfo complete = new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + VibratorInfo complete = new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{VibrationEffect.EFFECT_CLICK}, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); assertEquals(complete, complete); - assertEquals(complete, new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertEquals(complete, new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{VibrationEffect.EFFECT_CLICK}, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})); assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{}))); - assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS, + 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, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{}, new int[]{}))); - assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}))); - assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL, + assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, new int[]{VibrationEffect.EFFECT_CLICK}, null))); } @Test public void testSerialization() { - VibratorInfo original = new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS, + VibratorInfo original = new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS, new int[]{VibrationEffect.EFFECT_CLICK}, null); Parcel parcel = Parcel.obtain(); diff --git a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java index 366aabd183e3..fa824b140caa 100644 --- a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java +++ b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java @@ -37,8 +37,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Iterator; -import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) @@ -149,37 +147,6 @@ public final class CredentialManagementAppTest { private void assertCredentialManagementAppsEqual(CredentialManagementApp actual, CredentialManagementApp expected) { assertThat(actual.getPackageName(), is(expected.getPackageName())); - assertAuthenticationPoliciesEqual(actual.getAuthenticationPolicy(), - expected.getAuthenticationPolicy()); - } - - private void assertAuthenticationPoliciesEqual(AppUriAuthenticationPolicy actual, - AppUriAuthenticationPolicy expected) { - Iterator<Map.Entry<String, Map<Uri, String>>> actualIter = - actual.getAppAndUriMappings().entrySet().iterator(); - Iterator<Map.Entry<String, Map<Uri, String>>> expectedIter = - expected.getAppAndUriMappings().entrySet().iterator(); - - assertThat(actual.getAppAndUriMappings().size(), - is(expected.getAppAndUriMappings().size())); - while (actualIter.hasNext()) { - Map.Entry<String, Map<Uri, String>> actualAppToUri = actualIter.next(); - Map.Entry<String, Map<Uri, String>> expectedAppToUri = expectedIter.next(); - assertThat(actualAppToUri.getKey(), is(expectedAppToUri.getKey())); - assertUrisToAliasesEqual(actualAppToUri.getValue(), expectedAppToUri.getValue()); - } - } - - private void assertUrisToAliasesEqual(Map<Uri, String> actual, Map<Uri, String> expected) { - Iterator<Map.Entry<Uri, String>> actualIter = actual.entrySet().iterator(); - Iterator<Map.Entry<Uri, String>> expectedIter = expected.entrySet().iterator(); - - assertThat(actual.size(), is(expected.size())); - while (actualIter.hasNext()) { - Map.Entry<Uri, String> actualUriToAlias = actualIter.next(); - Map.Entry<Uri, String> expectedUriToAlias = expectedIter.next(); - assertThat(actualUriToAlias.getKey(), is(expectedUriToAlias.getKey())); - assertThat(actualUriToAlias.getValue(), is(expectedUriToAlias.getValue())); - } + assertThat(actual.getAuthenticationPolicy(), is(expected.getAuthenticationPolicy())); } } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java index ab24f89015c7..7e1e7f4bdd7f 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java @@ -33,9 +33,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import java.util.Arrays; -import java.util.List; - /** * Tests for AccessibilityInteractionClient */ @@ -65,7 +62,7 @@ public class AccessibilityInteractionClientTest { final long accessibilityNodeId = 0x4321L; AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain(); nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId); - mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection); + mMockConnection.mInfoToReturn = nodeFromConnection; AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId( MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null); @@ -75,7 +72,7 @@ public class AccessibilityInteractionClientTest { } private static class MockConnection extends AccessibilityServiceConnectionImpl { - List<AccessibilityNodeInfo> mInfosToReturn; + AccessibilityNodeInfo mInfoToReturn; @Override public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, @@ -83,7 +80,7 @@ public class AccessibilityInteractionClientTest { IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, Bundle arguments) { try { - callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId); + callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/core/tests/coretests/src/android/view/accessibility/OWNERS b/core/tests/coretests/src/android/view/accessibility/OWNERS new file mode 100644 index 000000000000..b74281edbf52 --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/accessibility/OWNERS diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java index 4680a640a7ff..ddb6729b55e1 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java @@ -17,14 +17,17 @@ package android.view.contentcapture; import static com.google.common.truth.Truth.assertThat; +import android.app.assist.ActivityId; import android.content.ComponentName; +import android.os.Binder; +import android.os.IBinder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** - * Unit test for {@link ContentCaptureEvent}. + * Unit test for {@link ContentCaptureContext}. * * <p>To run it: * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureContextTest} @@ -35,13 +38,17 @@ public class ContentCaptureContextTest { @Test public void testConstructorAdditionalFlags() { final ComponentName componentName = new ComponentName("component", "name"); + final IBinder token = new Binder(); final ContentCaptureContext ctx = new ContentCaptureContext(/* clientContext= */ null, - componentName, /* taskId= */ 666, /* displayId= */ 42, /* flags= */ 1); + new ActivityId(/* taskId= */ 666, token), componentName, /* displayId= */ + 42, /* flags= */ 1); final ContentCaptureContext newCtx = new ContentCaptureContext(ctx, /* extraFlags= */ 2); assertThat(newCtx.getFlags()).isEqualTo(3); - assertThat(newCtx.getActivityComponent()).isEqualTo(componentName); - assertThat(newCtx.getTaskId()).isEqualTo(666); + ActivityId activityId = newCtx.getActivityId(); + assertThat(activityId).isNotNull(); + assertThat(activityId.getTaskId()).isEqualTo(666); + assertThat(activityId.getToken()).isEqualTo(token); assertThat(newCtx.getDisplayId()).isEqualTo(42); assertThat(newCtx.getExtras()).isNull(); assertThat(newCtx.getLocusId()).isNull(); diff --git a/core/tests/coretests/src/com/android/internal/os/DischargedPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java index bec3d1644e81..cf126c62dac7 100644 --- a/core/tests/coretests/src/com/android/internal/os/DischargedPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java @@ -31,7 +31,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class DischargedPowerCalculatorTest { +public class BatteryChargeCalculatorTest { private static final double PRECISION = 0.00001; @Rule @@ -40,27 +40,41 @@ public class DischargedPowerCalculatorTest { @Test public void testDischargeTotals() { + BatteryChargeCalculator calculator = + new BatteryChargeCalculator(mStatsRule.getPowerProfile()); + final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); mStatsRule.setTime(1000, 1000); batteryStats.resetAllStatsCmdLocked(); batteryStats.setNoAutoReset(true); batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, - /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000, - 1_000_000, 1_000_000); + /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, + 1_000_000, 1_000_000, 1_000_000); batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, - /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000, - 2_000_000, 2_000_000); - - DischargedPowerCalculator calculator = - new DischargedPowerCalculator(mStatsRule.getPowerProfile()); + /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0, + 1_500_000, 1_500_000, 1_500_000); + batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, + /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, + 2_000_000, 2_000_000, 2_000_000); - final BatteryUsageStats batteryUsageStats = mStatsRule.apply(calculator); + BatteryUsageStats batteryUsageStats = mStatsRule.apply(calculator); assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(10); assertThat(batteryUsageStats.getDischargedPowerRange().getLower()) .isWithin(PRECISION).of(360.0); assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()) .isWithin(PRECISION).of(400.0); + assertThat(batteryUsageStats.getBatteryTimeRemainingMs()).isEqualTo(8_000_000); + assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(-1); + + // Plug in + batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_CHARGING, 100, + BatteryManager.BATTERY_PLUGGED_USB, 80, 72, 3700, 2_400_000, 4_000_000, 100, + 4_000_000, 4_000_000, 4_000_000); + + batteryUsageStats = mStatsRule.apply(calculator); + + assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(100_000); } } 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 74c37ada2054..ee472880b79f 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -23,6 +23,7 @@ import org.junit.runners.Suite; @Suite.SuiteClasses({ AmbientDisplayPowerCalculatorTest.class, AudioPowerCalculatorTest.class, + BatteryChargeCalculatorTest.class, BatteryStatsCpuTimesTest.class, BatteryStatsBackgroundStatsTest.class, BatteryStatsBinderCallStatsTest.class, @@ -49,7 +50,6 @@ import org.junit.runners.Suite; CameraPowerCalculatorTest.class, CpuPowerCalculatorTest.class, CustomMeasuredPowerCalculatorTest.class, - DischargedPowerCalculatorTest.class, FlashlightPowerCalculatorTest.class, GnssPowerCalculatorTest.class, IdlePowerCalculatorTest.class, diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index d2b20b4298d9..0808186b21c5 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -298,7 +298,7 @@ public class ActivityThreadClientTest { null /* pendingResults */, null /* pendingNewIntents */, null /* activityOptions */, true /* isForward */, null /* profilerInfo */, mThread /* client */, null /* asssitToken */, - null /* fixedRotationAdjustments */); + null /* fixedRotationAdjustments */, null /* shareableActivityToken */); } @Override diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp index 2f41b9084f40..89644e2320c1 100644 --- a/data/etc/car/Android.bp +++ b/data/etc/car/Android.bp @@ -146,6 +146,13 @@ prebuilt_etc { } prebuilt_etc { + name: "allowed_privapp_com.android.car.rotary", + sub_dir: "permissions", + src: "com.android.car.rotary.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "allowed_privapp_com.android.car.ui.paintbooth", sub_dir: "permissions", src: "com.android.car.ui.paintbooth.xml", diff --git a/data/etc/car/android.car.cluster.xml b/data/etc/car/android.car.cluster.xml index d7f29da7a356..de3accafa2fa 100644 --- a/data/etc/car/android.car.cluster.xml +++ b/data/etc/car/android.car.cluster.xml @@ -20,5 +20,7 @@ <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.car.permission.CAR_ENGINE_DETAILED"/> + <permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/> </privapp-permissions> </permissions> diff --git a/data/etc/car/com.android.car.bugreport.xml b/data/etc/car/com.android.car.bugreport.xml index c3642d886180..2ff98357f8bf 100644 --- a/data/etc/car/com.android.car.bugreport.xml +++ b/data/etc/car/com.android.car.bugreport.xml @@ -21,5 +21,6 @@ <permission name="android.permission.READ_LOGS"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/> + <permission name="android.car.permission.CAR_DRIVING_STATE"/> </privapp-permissions> </permissions> diff --git a/data/etc/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml index 0e49284d106f..ac16af348471 100644 --- a/data/etc/car/com.android.car.carlauncher.xml +++ b/data/etc/car/com.android.car.carlauncher.xml @@ -17,9 +17,10 @@ <permissions> <privapp-permissions package="com.android.car.carlauncher"> <permission name="android.permission.ACTIVITY_EMBEDDING"/> - <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> + <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/> </privapp-permissions> </permissions> diff --git a/data/etc/car/com.android.car.dialer.xml b/data/etc/car/com.android.car.dialer.xml index d44f5a1704a2..61ae53a30209 100644 --- a/data/etc/car/com.android.car.dialer.xml +++ b/data/etc/car/com.android.car.dialer.xml @@ -18,5 +18,6 @@ <privapp-permissions package="com.android.car.dialer"> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/> </privapp-permissions> </permissions> diff --git a/data/etc/car/com.android.car.hvac.xml b/data/etc/car/com.android.car.hvac.xml index d3631e067f8b..534d44da9a7c 100644 --- a/data/etc/car/com.android.car.hvac.xml +++ b/data/etc/car/com.android.car.hvac.xml @@ -17,5 +17,6 @@ <permissions> <privapp-permissions package="com.android.car.hvac"> <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.car.permission.CONTROL_CAR_CLIMATE"/> </privapp-permissions> </permissions> diff --git a/data/etc/car/com.android.car.radio.xml b/data/etc/car/com.android.car.radio.xml index d7853aba2d90..ed8652c01647 100644 --- a/data/etc/car/com.android.car.radio.xml +++ b/data/etc/car/com.android.car.radio.xml @@ -18,5 +18,7 @@ <privapp-permissions package="com.android.car.radio"> <permission name="android.permission.ACCESS_BROADCAST_RADIO"/> <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"/> </privapp-permissions> </permissions> diff --git a/data/etc/car/com.android.car.rotary.xml b/data/etc/car/com.android.car.rotary.xml new file mode 100644 index 000000000000..eddef1acbbc7 --- /dev/null +++ b/data/etc/car/com.android.car.rotary.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. + --> +<permissions> + <privapp-permissions package="com.android.car.rotary"> + <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> +</permissions> + diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml index bd30d7a61517..e6196c296552 100644 --- a/data/etc/car/com.google.android.car.kitchensink.xml +++ b/data/etc/car/com.google.android.car.kitchensink.xml @@ -51,5 +51,41 @@ <!-- use for rotary fragment to enable/disable packages related to rotary --> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> + + <!-- CarService permissions --> + <!-- TODO: Explain why so many permissions are required --> + <permission name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/> + <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_DISPLAY_IN_CLUSTER"/> + <permission name="android.car.permission.CAR_DRIVING_STATE"/> + <permission name="android.car.permission.CAR_DYNAMICS_STATE"/> + <permission name="android.car.permission.CAR_EXTERIOR_LIGHTS"/> + <permission name="android.car.permission.CAR_IDENTIFICATION"/> + <permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/> + <permission name="android.car.permission.CAR_MILEAGE"/> + <permission name="android.car.permission.CAR_MOCK_VEHICLE_HAL"/> + <permission name="android.car.permission.CAR_NAVIGATION_MANAGER"/> + <permission name="android.car.permission.CAR_POWER"/> + <permission name="android.car.permission.CAR_PROJECTION"/> + <permission name="android.car.permission.CAR_TIRES"/> + <permission name="android.car.permission.CAR_TEST_SERVICE"/> + <permission name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"/> + <permission name="android.car.permission.CAR_VENDOR_EXTENSION"/> + <permission name="android.car.permission.CONTROL_CAR_CLIMATE"/> + <permission name="android.car.permission.CONTROL_CAR_DOORS"/> + <permission name="android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS"/> + <permission name="android.car.permission.CONTROL_CAR_FEATURES"/> + <permission name="android.car.permission.CONTROL_CAR_MIRRORS"/> + <permission name="android.car.permission.CONTROL_CAR_SEATS"/> + <permission name="android.car.permission.CONTROL_CAR_WINDOWS"/> + <permission name="android.car.permission.GET_CAR_VENDOR_CATEGORY_INFO"/> + <permission name="android.car.permission.GET_CAR_VENDOR_CATEGORY_SEAT"/> + <permission name="android.car.permission.READ_CAR_STEERING"/> + <permission name="android.car.permission.SET_CAR_VENDOR_CATEGORY_INFO"/> + <permission name="android.car.permission.STORAGE_MONITORING"/> + <permission name="android.car.permission.VMS_PUBLISHER"/> + <permission name="android.car.permission.VMS_SUBSCRIBER"/> </privapp-permissions> </permissions> diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 3d964fb9bb87..f2a33de008d6 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -70,5 +70,6 @@ <permission name="android.permission.READ_WIFI_CREDENTIAL" /> <permission name="android.permission.USE_BACKGROUND_BLUR" /> <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" /> + <permission name="android.permission.FORCE_STOP_PACKAGES" /> </privapp-permissions> </permissions> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index ea42246e8262..77a38a9bb1a0 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -166,6 +166,7 @@ <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" /> <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" /> + <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" /> <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" /> @@ -176,6 +177,7 @@ <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" /> <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" /> <assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" /> + <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 3900d7e674ca..fae89d65ddd1 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -68,6 +68,11 @@ applications that come with the platform <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> </privapp-permissions> + <privapp-permissions package="com.android.imsserviceentitlement"> + <permission name="android.permission.MODIFY_PHONE_STATE" /> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> + </privapp-permissions> + <privapp-permissions package="com.android.launcher3"> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> </privapp-permissions> @@ -251,6 +256,7 @@ applications that come with the platform <!-- Permissions required for reading DeviceConfig --> <permission name="android.permission.READ_DEVICE_CONFIG" /> <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/> + <permission name="android.permission.MODIFY_QUIET_MODE"/> </privapp-permissions> <privapp-permissions package="com.android.providers.telephony"> @@ -478,6 +484,8 @@ applications that come with the platform <permission name="android.permission.SIGNAL_REBOOT_READINESS" /> <!-- Permission required for CTS test - PeopleManagerTest --> <permission name="android.permission.READ_PEOPLE_DATA" /> + <!-- Permission required for CTS test - UiTranslationManagerTest --> + <permission name="android.permission.MANAGE_UI_TRANSLATION" /> </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 cabfad44cfb9..b7bf8ab75799 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -43,6 +43,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-2093859262": { + "message": "setClientVisible: %s clientVisible=%b Callers=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowToken.java" + }, "-2072089308": { "message": "Attempted to add window with token that is a sub-window: %s. Aborting.", "level": "WARN", @@ -811,6 +817,12 @@ "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", @@ -823,12 +835,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java" }, - "-1144293044": { - "message": "SURFACE SET FREEZE LAYER: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowStateAnimator.java" - }, "-1142279614": { "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s", "level": "VERBOSE", @@ -1831,6 +1837,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "63329306": { + "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/WallpaperWindowToken.java" + }, "73987756": { "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s", "level": "INFO", @@ -2461,6 +2473,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "691515534": { + "message": " Commit wallpaper becoming invisible: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "693423992": { "message": "setAnimationLocked: setting mFocusMayChange true", "level": "INFO", diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 4f188cc03282..66b8eaec4bcf 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -245,7 +245,7 @@ <alias name="monaco" to="monospace" /> <family name="serif-monospace"> - <font weight="400" style="normal">CutiveMono.ttf</font> + <font weight="400" style="normal">CutiveMono-Regular.ttf</font> </family> <alias name="courier" to="serif-monospace" /> <alias name="courier new" to="serif-monospace" /> @@ -255,7 +255,7 @@ </family> <family name="cursive"> - <font weight="400" style="normal">DancingScript-Regular.ttf</font> + <font weight="400" style="normal">DancingScript.ttf</font> <font weight="700" style="normal">DancingScript-Bold.ttf</font> </family> diff --git a/errorprone/Android.bp b/errorprone/Android.bp index d1e94dfed4a8..a927f53e3b5f 100644 --- a/errorprone/Android.bp +++ b/errorprone/Android.bp @@ -36,11 +36,12 @@ java_library_host { java_test_host { name: "error_prone_android_framework_test", - test_suites: ["general-tests"], srcs: ["tests/java/**/*.java"], java_resource_dirs: ["tests/res"], java_resources: [":error_prone_android_framework_testdata"], static_libs: [ + "truth-prebuilt", + "kxml2-2.3.0", "error_prone_android_framework_lib", "error_prone_test_helpers", "hamcrest-library", @@ -48,6 +49,9 @@ java_test_host { "platform-test-annotations", "junit", ], + test_options: { + unit_test: true, + }, } filegroup { diff --git a/errorprone/TEST_MAPPING b/errorprone/TEST_MAPPING deleted file mode 100644 index ee4552fb3b33..000000000000 --- a/errorprone/TEST_MAPPING +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presubmit": [ - { - "name": "error_prone_android_framework_test" - } - ] -} diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 63df0db99764..95c7715a1688 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -136,7 +136,7 @@ public class FontListParser { customization.getAdditionalNamedFamilies(); parser.require(XmlPullParser.START_TAG, null, "familyset"); - while (parser.next() != XmlPullParser.END_TAG) { + while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { @@ -158,6 +158,12 @@ public class FontListParser { return new FontConfig(families, aliases, lastModifiedDate, configVersion); } + private static boolean keepReading(XmlPullParser parser) + throws XmlPullParserException, IOException { + int next = parser.next(); + return next != XmlPullParser.END_TAG && next != XmlPullParser.END_DOCUMENT; + } + /** * Read family tag in fonts.xml or oem_customization.xml */ @@ -168,7 +174,7 @@ public class FontListParser { final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); final List<FontConfig.Font> fonts = new ArrayList<>(); - while (parser.next() != XmlPullParser.END_TAG) { + while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; final String tag = parser.getName(); if (tag.equals(TAG_FONT)) { @@ -232,7 +238,7 @@ public class FontListParser { boolean isItalic = STYLE_ITALIC.equals(parser.getAttributeValue(null, ATTR_STYLE)); String fallbackFor = parser.getAttributeValue(null, ATTR_FALLBACK_FOR); StringBuilder filename = new StringBuilder(); - while (parser.next() != XmlPullParser.END_TAG) { + while (keepReading(parser)) { if (parser.getEventType() == XmlPullParser.TEXT) { filename.append(parser.getText()); } @@ -359,6 +365,8 @@ public class FontListParser { case XmlPullParser.END_TAG: depth--; break; + case XmlPullParser.END_DOCUMENT: + return; } } } diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index f6f770be3de8..da5162b9e15e 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -719,11 +719,11 @@ public final class RenderNode { /** @hide */ public boolean stretch(float left, float top, float right, float bottom, float vecX, float vecY, float maxStretchAmount) { - if (1.0 < vecX || vecX < -1.0) { - throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX); + if (Float.isInfinite(vecX) || Float.isNaN(vecX)) { + throw new IllegalArgumentException("vecX must be a finite, non-NaN value " + vecX); } - if (1.0 < vecY || vecY < -1.0) { - throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY); + if (Float.isInfinite(vecY) || Float.isNaN(vecY)) { + throw new IllegalArgumentException("vecY must be a finite, non-NaN value " + vecY); } if (top >= bottom || left >= right) { throw new IllegalArgumentException( @@ -734,7 +734,16 @@ public final class RenderNode { throw new IllegalArgumentException( "The max stretch amount must be >0, got " + maxStretchAmount); } - return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount); + return nStretch( + mNativeRenderNode, + left, + top, + right, + bottom, + vecX, + vecY, + maxStretchAmount + ); } /** diff --git a/keystore/java/android/security/AppUriAuthenticationPolicy.java b/keystore/java/android/security/AppUriAuthenticationPolicy.java index 0244ce97c0d4..df79912128fe 100644 --- a/keystore/java/android/security/AppUriAuthenticationPolicy.java +++ b/keystore/java/android/security/AppUriAuthenticationPolicy.java @@ -238,4 +238,21 @@ public final class AppUriAuthenticationPolicy implements Parcelable { return aliases; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AppUriAuthenticationPolicy)) { + return false; + } + AppUriAuthenticationPolicy other = (AppUriAuthenticationPolicy) obj; + return Objects.equals(mAppToUris, other.mAppToUris); + } + + @Override + public int hashCode() { + return mAppToUris.hashCode(); + } + } diff --git a/keystore/java/android/security/LegacyVpnProfileStore.java b/keystore/java/android/security/LegacyVpnProfileStore.java new file mode 100644 index 000000000000..41cfb2707fcf --- /dev/null +++ b/keystore/java/android/security/LegacyVpnProfileStore.java @@ -0,0 +1,142 @@ +/* + * 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.security; + +import android.annotation.NonNull; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.security.keystore.AndroidKeyStoreProvider; +import android.security.vpnprofilestore.IVpnProfileStore; +import android.util.Log; + +/** + * @hide This class allows legacy VPN access to its profiles that were stored in Keystore. + * The storage of unstructured blobs in Android Keystore is going away, because there is no + * architectural or security benefit of storing profiles in keystore over storing them + * in the file system. This class allows access to the blobs that still exist in keystore. + * And it stores new blob in a database that is still owned by Android Keystore. + */ +public class LegacyVpnProfileStore { + private static final String TAG = "LegacyVpnProfileStore"; + + public static final int SYSTEM_ERROR = IVpnProfileStore.ERROR_SYSTEM_ERROR; + public static final int PROFILE_NOT_FOUND = IVpnProfileStore.ERROR_PROFILE_NOT_FOUND; + + private static final String VPN_PROFILE_STORE_SERVICE_NAME = "android.security.vpnprofilestore"; + + private static IVpnProfileStore getService() { + return IVpnProfileStore.Stub.asInterface( + ServiceManager.checkService(VPN_PROFILE_STORE_SERVICE_NAME)); + } + + /** + * Stores the profile under the alias in the profile database. Existing profiles by the + * same name will be replaced. + * @param alias The name of the profile + * @param profile The profile. + * @return true if the profile was successfully added. False otherwise. + * @hide + */ + public static boolean put(@NonNull String alias, @NonNull byte[] profile) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + getService().put(alias, profile); + return true; + } else { + return KeyStore.getInstance().put( + alias, profile, KeyStore.UID_SELF, 0); + } + } catch (Exception e) { + Log.e(TAG, "Failed to put vpn profile.", e); + return false; + } + } + + /** + * Retrieves a profile by the name alias from the profile database. + * @param alias Name of the profile to retrieve. + * @return The unstructured blob, that is the profile that was stored using + * LegacyVpnProfileStore#put or with + * android.security.Keystore.put(Credentials.VPN + alias). + * Returns null if no profile was found. + * @hide + */ + public static byte[] get(@NonNull String alias) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + return getService().get(alias); + } else { + return KeyStore.getInstance().get(alias, true /* suppressKeyNotFoundWarning */); + } + } catch (ServiceSpecificException e) { + if (e.errorCode != PROFILE_NOT_FOUND) { + Log.e(TAG, "Failed to get vpn profile.", e); + } + } catch (Exception e) { + Log.e(TAG, "Failed to get vpn profile.", e); + } + return null; + } + + /** + * Removes a profile by the name alias from the profile database. + * @param alias Name of the profile to be removed. + * @return True if a profile was removed. False if no such profile was found. + * @hide + */ + public static boolean remove(@NonNull String alias) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + getService().remove(alias); + return true; + } else { + return KeyStore.getInstance().delete(alias); + } + } catch (ServiceSpecificException e) { + if (e.errorCode != PROFILE_NOT_FOUND) { + Log.e(TAG, "Failed to remove vpn profile.", e); + } + } catch (Exception e) { + Log.e(TAG, "Failed to remove vpn profile.", e); + } + return false; + } + + /** + * Lists the vpn profiles stored in the database. + * @return An array of strings representing the aliases stored in the profile database. + * The return value may be empty but never null. + * @hide + */ + public static @NonNull String[] list(@NonNull String prefix) { + try { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + final String[] aliases = getService().list(prefix); + for (int i = 0; i < aliases.length; ++i) { + aliases[i] = aliases[i].substring(prefix.length()); + } + return aliases; + } else { + final String[] result = KeyStore.getInstance().list(prefix); + return result != null ? result : new String[0]; + } + } catch (Exception e) { + Log.e(TAG, "Failed to list vpn profiles.", e); + } + return new String[0]; + } +} diff --git a/keystore/java/android/security/UrisToAliases.java b/keystore/java/android/security/UrisToAliases.java index 65d433abe166..9a8b659f3db4 100644 --- a/keystore/java/android/security/UrisToAliases.java +++ b/keystore/java/android/security/UrisToAliases.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * The mapping from URI to alias, which determines the alias to use when the user visits a URI. @@ -135,4 +136,21 @@ public final class UrisToAliases implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeMap(mUrisToAliases); } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof UrisToAliases)) { + return false; + } + UrisToAliases other = (UrisToAliases) obj; + return Objects.equals(mUrisToAliases, other.mUrisToAliases); + } + + @Override + public int hashCode() { + return mUrisToAliases.hashCode(); + } } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index c79c12cd3343..72735a787b7f 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -279,8 +279,10 @@ import javax.security.auth.x500.X500Principal; * } */ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs { - private static final X500Principal DEFAULT_CERT_SUBJECT = + private static final X500Principal DEFAULT_ATTESTATION_CERT_SUBJECT = new X500Principal("CN=Android Keystore Key"); + private static final X500Principal DEFAULT_SELF_SIGNED_CERT_SUBJECT = + new X500Principal("CN=Fake"); private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1"); private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970 private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 @@ -366,7 +368,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } if (certificateSubject == null) { - certificateSubject = DEFAULT_CERT_SUBJECT; + if (attestationChallenge == null) { + certificateSubject = DEFAULT_SELF_SIGNED_CERT_SUBJECT; + } else { + certificateSubject = DEFAULT_ATTESTATION_CERT_SUBJECT; + } } if (certificateNotBefore == null) { certificateNotBefore = DEFAULT_CERT_NOT_BEFORE; 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 7376d9898ab8..85bd24c1c2bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -24,7 +24,9 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; 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.transition.Transitions; import java.util.Optional; @@ -41,9 +43,11 @@ public class ShellInitImpl { private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<AppPairsController> mAppPairsOptional; + private final Optional<PipTouchHandler> mPipTouchHandlerOptional; private final FullscreenTaskListener mFullscreenTaskListener; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; + private final Optional<StartingSurface> mStartingSurfaceOptional; private final InitImpl mImpl = new InitImpl(); @@ -53,6 +57,8 @@ public class ShellInitImpl { Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, + Optional<StartingSurface> startingSurfaceOptional, + Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, ShellExecutor mainExecutor) { @@ -62,6 +68,8 @@ public class ShellInitImpl { legacySplitScreenOptional, splitScreenOptional, appPairsOptional, + startingSurfaceOptional, + pipTouchHandlerOptional, fullscreenTaskListener, transitions, mainExecutor).mImpl; @@ -73,6 +81,8 @@ public class ShellInitImpl { Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, + Optional<StartingSurface> startingSurfaceOptional, + Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, ShellExecutor mainExecutor) { @@ -83,8 +93,10 @@ public class ShellInitImpl { mSplitScreenOptional = splitScreenOptional; mAppPairsOptional = appPairsOptional; mFullscreenTaskListener = fullscreenTaskListener; + mPipTouchHandlerOptional = pipTouchHandlerOptional; mTransitions = transitions; mMainExecutor = mainExecutor; + mStartingSurfaceOptional = startingSurfaceOptional; } private void init() { @@ -93,6 +105,7 @@ public class ShellInitImpl { mShellTaskOrganizer.addListenerForType( mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); + mStartingSurfaceOptional.ifPresent(mShellTaskOrganizer::initStartingSurface); // Register the shell organizer mShellTaskOrganizer.registerOrganizer(); @@ -105,6 +118,11 @@ public class ShellInitImpl { if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.register(mShellTaskOrganizer); } + + // TODO(b/181599115): This should really be the pip controller, but until we can provide the + // controller instead of the feature interface, can just initialize the touch handler if + // needed + mPipTouchHandlerOptional.ifPresent((handler) -> handler.init()); } @ExternalThread 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 efc55c4fbe31..9ddeb2fc6e1e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -44,7 +44,7 @@ import com.android.internal.annotations.VisibleForTesting; 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.StartingSurfaceDrawer; +import com.android.wm.shell.startingsurface.StartingSurface; import java.io.PrintWriter; import java.util.ArrayList; @@ -110,7 +110,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { private final ArrayMap<IBinder, TaskListener> mLaunchCookieToListener = new ArrayMap<>(); private final Object mLock = new Object(); - private final StartingSurfaceDrawer mStartingSurfaceDrawer; + private StartingSurface mStartingSurface; /** * In charge of showing size compat UI. Can be {@code null} if device doesn't support size @@ -120,23 +120,19 @@ public class ShellTaskOrganizer extends TaskOrganizer { private final SizeCompatUIController mSizeCompatUI; public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { - this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */, - new StartingSurfaceDrawer(context, mainExecutor)); + this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable SizeCompatUIController sizeCompatUI) { - this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, - new StartingSurfaceDrawer(context, mainExecutor)); + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable SizeCompatUIController sizeCompatUI, - StartingSurfaceDrawer startingSurfaceDrawer) { + Context context, @Nullable SizeCompatUIController sizeCompatUI) { super(taskOrganizerController, mainExecutor); mSizeCompatUI = sizeCompatUI; - mStartingSurfaceDrawer = startingSurfaceDrawer; } @Override @@ -163,6 +159,15 @@ public class ShellTaskOrganizer extends TaskOrganizer { } /** + * @hide + */ + public void initStartingSurface(StartingSurface startingSurface) { + synchronized (mLock) { + mStartingSurface = startingSurface; + } + } + + /** * Adds a listener for a specific task id. */ public void addListenerForTaskId(TaskListener listener, int taskId) { @@ -254,17 +259,23 @@ public class ShellTaskOrganizer extends TaskOrganizer { @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { - mStartingSurfaceDrawer.addStartingWindow(info, appToken); + if (mStartingSurface != null) { + mStartingSurface.addStartingWindow(info, appToken); + } } @Override public void removeStartingWindow(int taskId) { - mStartingSurfaceDrawer.removeStartingWindow(taskId); + if (mStartingSurface != null) { + mStartingSurface.removeStartingWindow(taskId); + } } @Override public void copySplashScreenView(int taskId) { - mStartingSurfaceDrawer.copySplashScreenView(taskId); + if (mStartingSurface != null) { + mStartingSurface.copySplashScreenView(taskId); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 1320780bfb8f..d31e637b778e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -327,6 +327,10 @@ public class BubbleController { return mImpl; } + public ShellExecutor getMainExecutor() { + return mMainExecutor; + } + /** * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 2f31acd41408..9ef3fb54fb35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -340,7 +340,7 @@ public class BubbleExpandedView extends LinearLayout { mSettingsIcon.setVisibility(GONE); } else { mTaskView = new TaskView(mContext, mController.getTaskOrganizer()); - mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener); + mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener); mExpandedViewContainer.addView(mTaskView); bringChildToFront(mTaskView); } 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 6f5f2eb5723c..aab2334f8255 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 @@ -234,8 +234,8 @@ public class DragAndDropPolicy { final UserHandle user = intent.getParcelableExtra(EXTRA_USER); mStarter.startShortcut(packageName, id, stage, position, opts, user); } else { - mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), stage, position, - opts); + mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), + mContext, null, stage, position, opts); } } @@ -295,7 +295,8 @@ public class DragAndDropPolicy { @Nullable Bundle options); void startShortcut(String packageName, String shortcutId, @StageType int stage, @StagePosition int position, @Nullable Bundle options, UserHandle user); - void startIntent(PendingIntent intent, @StageType int stage, @StagePosition int position, + void startIntent(PendingIntent intent, Context context, Intent fillInIntent, + @StageType int stage, @StagePosition int position, @Nullable Bundle options); void enterSplitScreen(int taskId, boolean leftOrTop); void exitSplitScreen(); @@ -336,10 +337,11 @@ public class DragAndDropPolicy { } @Override - public void startIntent(PendingIntent intent, int stage, int position, + public void startIntent(PendingIntent intent, Context context, + @Nullable Intent fillInIntent, int stage, int position, @Nullable Bundle options) { try { - intent.send(null, 0, null, 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); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java index 05526018d73f..f4c0f9384705 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java @@ -52,6 +52,9 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { private final SyncTransactionQueue mSyncQueue; private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>(); + // TODO(shell-transitions): Remove when switched to shell-transitions. + private final SparseArray<Point> mPositionByTaskId = new SparseArray<>(); + RunningTaskInfo mPrimary; RunningTaskInfo mSecondary; SurfaceControl mPrimarySurface; @@ -167,6 +170,7 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskVanished(RunningTaskInfo taskInfo) { synchronized (this) { + mPositionByTaskId.remove(taskInfo.taskId); if (taskInfo.hasParentTask()) { mLeashByTaskId.remove(taskInfo.taskId); return; @@ -200,16 +204,24 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { } synchronized (this) { if (taskInfo.hasParentTask()) { + // changed messages are noisy since it reports on every ensureVisibility. This + // conflicts with legacy app-transitions which "swaps" the position to a + // leash. For now, only update when position actually changes to avoid + // poorly-timed duplicate calls. + if (taskInfo.positionInParent.equals(mPositionByTaskId.get(taskInfo.taskId))) { + return; + } handleChildTaskChanged(taskInfo); - return; + } else { + handleTaskInfoChanged(taskInfo); } - - handleTaskInfoChanged(taskInfo); + mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent)); } } private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { mLeashByTaskId.put(taskInfo.taskId, leash); + mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent)); if (Transitions.ENABLE_SHELL_TRANSITIONS) return; updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index ac5d14c802d8..702385ecc3d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -54,6 +54,7 @@ public class PipBoundsAlgorithm { private float mMaxAspectRatio; private int mDefaultStackGravity; private int mDefaultMinSize; + private int mOverridableMinSize; private Point mScreenEdgeInsets; public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState) { @@ -78,6 +79,8 @@ public class PipBoundsAlgorithm { com.android.internal.R.integer.config_defaultPictureInPictureGravity); mDefaultMinSize = res.getDimensionPixelSize( com.android.internal.R.dimen.default_minimal_size_pip_resizable_task); + mOverridableMinSize = res.getDimensionPixelSize( + com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task); final String screenEdgeInsetsDpString = res.getString( com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets); final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() @@ -155,7 +158,10 @@ public class PipBoundsAlgorithm { // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout> // without minWidth/minHeight if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) { - return new Size(windowLayout.minWidth, windowLayout.minHeight); + // If either dimension is smaller than the allowed minimum, adjust them + // according to mOverridableMinSize + return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize), + Math.max(windowLayout.minHeight, mOverridableMinSize)); } return null; } 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 843e27f1b651..7ed7fd0096bd 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 @@ -172,6 +172,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }; private ActivityManager.RunningTaskInfo mTaskInfo; + // To handle the edge case that onTaskInfoChanged callback is received during the entering + // PiP transition, where we do not want to intercept the transition but still want to apply the + // changed RunningTaskInfo when it finishes. + private ActivityManager.RunningTaskInfo mDeferredTaskInfo; private WindowContainerToken mToken; private SurfaceControl mLeash; private State mState = State.UNDEFINED; @@ -520,12 +524,18 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipTransitionController.sendOnPipTransitionStarted(direction); } - private void sendOnPipTransitionFinished( + @VisibleForTesting + void sendOnPipTransitionFinished( @PipAnimationController.TransitionDirection int direction) { if (direction == TRANSITION_DIRECTION_TO_PIP) { mState = State.ENTERED_PIP; } mPipTransitionController.sendOnPipTransitionFinished(direction); + // Apply the deferred RunningTaskInfo if applicable after all proper callbacks are sent. + if (direction == TRANSITION_DIRECTION_TO_PIP && mDeferredTaskInfo != null) { + onTaskInfoChanged(mDeferredTaskInfo); + mDeferredTaskInfo = null; + } } private void sendOnPipTransitionCancelled( @@ -567,6 +577,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken"); + if (mState != State.ENTERED_PIP) { + Log.d(TAG, "Defer onTaskInfoChange in current state: " + mState); + mDeferredTaskInfo = info; + return; + } mPipBoundsState.setLastPipComponentName(info.topActivity); mPipBoundsState.setOverrideMinSize( mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index d9a7bdb2eca6..9ee6a221c80c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -87,7 +87,7 @@ public class PipDismissTargetHandler { SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); // Allow dragging the PIP to a location to close it - private final boolean mEnableDismissDragToEdge; + private boolean mEnableDismissDragToEdge; private int mDismissAreaHeight; @@ -104,67 +104,66 @@ public class PipDismissTargetHandler { mMotionHelper = motionHelper; mMainExecutor = mainExecutor; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + } - Resources res = context.getResources(); + public void init() { + Resources res = mContext.getResources(); mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); - mMainExecutor.execute(() -> { - mTargetView = new DismissCircleView(context); - mTargetViewContainer = new FrameLayout(context); - mTargetViewContainer.setBackgroundDrawable( - context.getDrawable(R.drawable.floating_dismiss_gradient_transition)); - mTargetViewContainer.setClipChildren(false); - mTargetViewContainer.addView(mTargetView); - - mMagnetizedPip = mMotionHelper.getMagnetizedPip(); - mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); - updateMagneticTargetSize(); - - mMagnetizedPip.setAnimateStuckToTarget( - (target, velX, velY, flung, after) -> { - if (mEnableDismissDragToEdge) { - mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, - after); - } - return Unit.INSTANCE; - }); - mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { - @Override - public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - // Show the dismiss target, in case the initial touch event occurred within - // the magnetic field radius. + mTargetView = new DismissCircleView(mContext); + mTargetViewContainer = new FrameLayout(mContext); + mTargetViewContainer.setBackgroundDrawable( + mContext.getDrawable(R.drawable.floating_dismiss_gradient_transition)); + mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.addView(mTargetView); + + mMagnetizedPip = mMotionHelper.getMagnetizedPip(); + mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); + updateMagneticTargetSize(); + + mMagnetizedPip.setAnimateStuckToTarget( + (target, velX, velY, flung, after) -> { if (mEnableDismissDragToEdge) { - showDismissTargetMaybe(); + mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after); } + return Unit.INSTANCE; + }); + mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + // Show the dismiss target, in case the initial touch event occurred within + // the magnetic field radius. + if (mEnableDismissDragToEdge) { + showDismissTargetMaybe(); } + } - @Override - public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, - float velX, float velY, boolean wasFlungOut) { - if (wasFlungOut) { - mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); - hideDismissTargetMaybe(); - } else { - mMotionHelper.setSpringingToTouch(true); - } + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + if (wasFlungOut) { + mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); + hideDismissTargetMaybe(); + } else { + mMotionHelper.setSpringingToTouch(true); } + } - @Override - public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mMainExecutor.executeDelayed(() -> { - mMotionHelper.notifyDismissalPending(); - mMotionHelper.animateDismiss(); - hideDismissTargetMaybe(); - - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE); - }, 0); - } - }); + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMainExecutor.executeDelayed(() -> { + mMotionHelper.notifyDismissalPending(); + mMotionHelper.animateDismiss(); + hideDismissTargetMaybe(); - mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE); + }, 0); + } }); + + mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); } /** 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 eae8945ce6be..d742aa688fe7 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 @@ -102,7 +102,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()} * using physics animations. */ - private final PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator; + private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator; private MagnetizedObject<Rect> mMagnetizedPip; @@ -171,7 +171,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController, - FloatingContentCoordinator floatingContentCoordinator, ShellExecutor mainExecutor) { + FloatingContentCoordinator floatingContentCoordinator) { mContext = context; mPipTaskOrganizer = pipTaskOrganizer; mPipBoundsState = pipBoundsState; @@ -179,15 +179,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); - mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( - mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); - - // Need to get the shell main thread sf vsync animation handler - mainExecutor.execute(() -> { - mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler( - mSfAnimationHandlerThreadLocal.get()); - }); - mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { mPipTaskOrganizer.scheduleUserResizePip(getBounds(), @@ -196,6 +187,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, }; } + public void init() { + // Note: Needs to get the shell main thread sf vsync animation handler + mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler( + mSfAnimationHandlerThreadLocal.get()); + } + @NonNull @Override public Rect getFloatingBoundsOnScreen() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 6a24ebf244d8..c726c012c6a0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -134,8 +134,10 @@ public class PipResizeGestureHandler { mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; + } - context.getDisplay().getRealSize(mMaxSize); + public void init() { + mContext.getDisplay().getRealSize(mMaxSize); reloadResources(); mEnablePinchResize = DeviceConfig.getBoolean( 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 5e23281b3438..543ecfcf1a33 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 @@ -71,12 +71,13 @@ public class PipTouchHandler { private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; // Allow PIP to resize to a slightly bigger state upon touch - private final boolean mEnableResize; + private boolean mEnableResize; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final @NonNull PipBoundsState mPipBoundsState; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; + private final ShellExecutor mMainExecutor; private PipResizeGestureHandler mPipResizeGestureHandler; private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener; @@ -166,16 +167,18 @@ public class PipTouchHandler { ShellExecutor mainExecutor) { // Initialize the Pip input consumer mContext = context; + mMainExecutor = mainExecutor; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; + mFloatingContentCoordinator = floatingContentCoordinator; mMenuController.addListener(new PipMenuListener()); mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer, mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), pipTransitionController, - floatingContentCoordinator, mainExecutor); + floatingContentCoordinator); mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, mMotionHelper, pipTaskOrganizer, this::getMovementBounds, @@ -199,22 +202,26 @@ public class PipTouchHandler { }, menuController::hideMenu, mainExecutor); + mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState, + mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), + this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor); + } - Resources res = context.getResources(); + public void init() { + Resources res = mContext.getResources(); mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu); reloadResources(); - mFloatingContentCoordinator = floatingContentCoordinator; - mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState, - mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), - this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor); + mMotionHelper.init(); + mPipResizeGestureHandler.init(); + mPipDismissTargetHandler.init(); mEnableStash = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, PIP_STASHING, /* defaultValue = */ true); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mainExecutor, + mMainExecutor, properties -> { if (properties.getKeyset().contains(PIP_STASHING)) { mEnableStash = properties.getBoolean( @@ -226,7 +233,7 @@ public class PipTouchHandler { PIP_STASH_MINIMUM_VELOCITY_THRESHOLD, DEFAULT_STASH_VELOCITY_THRESHOLD); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mainExecutor, + mMainExecutor, properties -> { if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) { mStashVelocityThreshold = properties.getFloat( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java index 32f3648be19a..c6d994ecde8d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java @@ -280,7 +280,7 @@ class SizeCompatUILayout { : stableBounds.right - taskBounds.left - mButtonSize; final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize; - mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY)); + updateSurfacePosition(leash, positionX, positionY); } void updateHintSurfacePosition() { @@ -303,7 +303,16 @@ class SizeCompatUILayout { final int positionY = stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight(); - mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY)); + updateSurfacePosition(leash, positionX, positionY); + } + + private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) { + mSyncQueue.runInSync(t -> { + t.setPosition(leash, positionX, positionY); + // The size compat UI should be the topmost child of the Task in case there can be more + // than one children. + t.setLayer(leash, Integer.MAX_VALUE); + }); } int getDisplayId() { 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 7ca569349633..25a84bd46484 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 @@ -19,18 +19,17 @@ package com.android.wm.shell.splitscreen; import android.annotation.IntDef; import android.app.ActivityManager; import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropPolicy; -import java.io.PrintWriter; - /** * Interface to engage split-screen feature. * TODO: Figure out which of these are actually needed outside of the Shell @@ -87,7 +86,7 @@ public interface SplitScreen extends DragAndDropPolicy.Starter { /** Callback interface for listening to changes in a split-screen stage. */ interface SplitScreenListener { void onStagePositionChanged(@StageType int stage, @StagePosition int position); - void onTaskStageChanged(int taskId, @StageType int stage); + void onTaskStageChanged(int taskId, @StageType int stage, boolean visible); } /** @return {@code true} if split-screen is currently visible. */ @@ -118,6 +117,7 @@ public interface SplitScreen extends DragAndDropPolicy.Starter { @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, - @StageType int stage, @StagePosition int position, @Nullable Bundle options); + void startIntent(PendingIntent intent, Context context, + @Nullable Intent fillInIntent, @StageType int stage, + @StagePosition int position, @Nullable Bundle options); } 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 11548adaf5d1..bb6f6f259a1e 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 @@ -30,6 +30,7 @@ import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; import android.content.pm.LauncherApps; import android.graphics.Rect; import android.os.Bundle; @@ -171,12 +172,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { } } - public void startIntent(PendingIntent intent, @SplitScreen.StageType int stage, + public void startIntent(PendingIntent intent, Context context, + Intent fillInIntent, @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position, @Nullable Bundle options) { options = resolveStartStage(stage, position, options); try { - intent.send(null, 0, null, null, null, null, options); + intent.send(context, 0, fillInIntent, null, null, null, options); } catch (PendingIntent.CanceledException e) { Slog.e(TAG, "Failed to launch activity", e); } @@ -348,10 +350,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { } @Override - public void startIntent(PendingIntent intent, int stage, int position, - @Nullable Bundle options) { + public void startIntent(PendingIntent intent, Context context, Intent fillInIntent, + int stage, int position, @Nullable Bundle options) { mMainExecutor.execute(() -> { - SplitScreenController.this.startIntent(intent, stage, position, options); + SplitScreenController.this.startIntent(intent, context, fillInIntent, stage, + position, options); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 22c97515ad76..b180bb52e3e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -225,7 +225,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } private void onStageChildTaskStatusChanged( - StageListenerImpl stageListener, int taskId, boolean present) { + StageListenerImpl stageListener, int taskId, boolean present, boolean visible) { int stage; if (present) { @@ -236,7 +236,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } for (int i = mListeners.size() - 1; i >= 0; --i) { - mListeners.get(i).onTaskStageChanged(taskId, stage); + mListeners.get(i).onTaskStageChanged(taskId, stage, visible); } } @@ -362,8 +362,9 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, @Override public void onSnappedToDismiss(boolean bottomOrRight) { - final boolean mainStageToTop = bottomOrRight - && mSideStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT; + final boolean mainStageToTop = + bottomOrRight ? mSideStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT + : mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT; exitSplitScreen(mainStageToTop ? mMainStage : mSideStage); } @@ -494,8 +495,8 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } @Override - public void onChildTaskStatusChanged(int taskId, boolean present) { - StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present); + public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) { + StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 10c742b69578..b8cdc4ab4d75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -58,7 +58,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { public interface StageListenerCallbacks { void onRootTaskAppeared(); void onStatusChanged(boolean visible, boolean hasChildren); - void onChildTaskStatusChanged(int taskId, boolean present); + void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); void onRootTaskVanished(); } private final StageListenerCallbacks mCallbacks; @@ -88,7 +88,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenLeashes.put(taskId, leash); mChildrenTaskInfo.put(taskId, taskInfo); updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); - mCallbacks.onChildTaskStatusChanged(taskId, true /* present */); + mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); @@ -105,6 +105,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); updateChildTaskSurface( taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); + mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, + taskInfo.isVisible); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); @@ -123,7 +125,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenTaskInfo.remove(taskId); mChildrenLeashes.remove(taskId); sendStatusChanged(); - mCallbacks.onChildTaskStatusChanged(taskId, false /* present */); + mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); @@ -152,7 +154,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, @SplitScreen.StageType int stage) { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - listener.onTaskStageChanged(mChildrenTaskInfo.keyAt(i), stage); + int taskId = mChildrenTaskInfo.keyAt(i); + listener.onTaskStageChanged(taskId, stage, + mChildrenTaskInfo.get(taskId).isVisible); } } 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 new file mode 100644 index 000000000000..2c4ceffcb8f5 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.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.wm.shell.startingsurface; + +import android.os.IBinder; +import android.window.StartingWindowInfo; + +/** + * 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); + /** + * Called when the Task wants to copy the splash screen. + * @param taskId + */ + void copySplashScreenView(int taskId); +} 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 5332291fd1bd..814407164750 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 @@ -16,15 +16,9 @@ package com.android.wm.shell.startingsurface; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.content.Context.CONTEXT_RESTRICTED; import static android.content.res.Configuration.EMPTY; import static android.view.Display.DEFAULT_DISPLAY; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; @@ -37,7 +31,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.DisplayManager; import android.os.IBinder; -import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -46,7 +39,6 @@ import android.view.WindowManager; import android.window.SplashScreenView; import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.StartingWindowInfo; -import android.window.TaskOrganizer; import android.window.TaskSnapshot; import com.android.internal.R; @@ -56,19 +48,13 @@ import com.android.wm.shell.common.ShellExecutor; import java.util.function.Consumer; /** - * Implementation to draw the starting window to an application, and remove the starting window - * until the application displays its own window. - * - * When receive {@link TaskOrganizer#addStartingWindow} callback, use this class to create a - * starting window and attached to the Task, then when the Task want to remove the starting window, - * the TaskOrganizer will receive {@link TaskOrganizer#removeStartingWindow} callback then use this - * class to remove the starting window of the Task. + * A class which able to draw splash screen or snapshot as the starting window for a task. * @hide */ public class StartingSurfaceDrawer { static final String TAG = StartingSurfaceDrawer.class.getSimpleName(); - static final boolean DEBUG_SPLASH_SCREEN = false; - static final boolean DEBUG_TASK_SNAPSHOT = false; + static final boolean DEBUG_SPLASH_SCREEN = StartingWindowController.DEBUG_SPLASH_SCREEN; + static final boolean DEBUG_TASK_SNAPSHOT = StartingWindowController.DEBUG_TASK_SNAPSHOT; private final Context mContext; private final DisplayManager mDisplayManager; @@ -107,106 +93,10 @@ public class StartingSurfaceDrawer { return context.createDisplayContext(targetDisplay); } - private static class PreferredStartingTypeHelper { - private static final int STARTING_TYPE_NO = 0; - private static final int STARTING_TYPE_SPLASH_SCREEN = 1; - private static final int STARTING_TYPE_SNAPSHOT = 2; - - TaskSnapshot mSnapshot; - int mPreferredType; - - PreferredStartingTypeHelper(StartingWindowInfo taskInfo) { - final int parameter = taskInfo.startingWindowTypeParameter; - final boolean newTask = (parameter & TYPE_PARAMETER_NEW_TASK) != 0; - final boolean taskSwitch = (parameter & TYPE_PARAMETER_TASK_SWITCH) != 0; - final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0; - final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0; - final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0; - mPreferredType = preferredStartingWindowType(taskInfo, newTask, taskSwitch, - processRunning, allowTaskSnapshot, activityCreated); - } - - // reference from ActivityRecord#getStartingWindowType - private int preferredStartingWindowType(StartingWindowInfo windowInfo, - boolean newTask, boolean taskSwitch, boolean processRunning, - boolean allowTaskSnapshot, boolean activityCreated) { - if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { - Slog.d(TAG, "preferredStartingWindowType newTask " + newTask - + " taskSwitch " + taskSwitch - + " processRunning " + processRunning - + " allowTaskSnapshot " + allowTaskSnapshot - + " activityCreated " + activityCreated); - } - - if (newTask || !processRunning || (taskSwitch && !activityCreated)) { - return STARTING_TYPE_SPLASH_SCREEN; - } else if (taskSwitch && allowTaskSnapshot) { - final TaskSnapshot snapshot = getTaskSnapshot(windowInfo.taskInfo.taskId); - if (isSnapshotCompatible(windowInfo, snapshot)) { - return STARTING_TYPE_SNAPSHOT; - } - if (windowInfo.taskInfo.topActivityType != ACTIVITY_TYPE_HOME) { - return STARTING_TYPE_SPLASH_SCREEN; - } - return STARTING_TYPE_NO; - } else { - return STARTING_TYPE_NO; - } - } - - /** - * Returns {@code true} if the task snapshot is compatible with this activity (at least the - * rotation must be the same). - */ - private boolean isSnapshotCompatible(StartingWindowInfo windowInfo, TaskSnapshot snapshot) { - if (snapshot == null) { - if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { - Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId); - } - return false; - } - - final int taskRotation = windowInfo.taskInfo.configuration - .windowConfiguration.getRotation(); - final int snapshotRotation = snapshot.getRotation(); - if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { - Slog.d(TAG, "isSnapshotCompatible rotation " + taskRotation - + " snapshot " + snapshotRotation); - } - return taskRotation == snapshotRotation; - } - - private TaskSnapshot getTaskSnapshot(int taskId) { - if (mSnapshot != null) { - return mSnapshot; - } - try { - mSnapshot = ActivityTaskManager.getService().getTaskSnapshot(taskId, - false/* isLowResolution */); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to get snapshot for task: " + taskId + ", from: " + e); - return null; - } - return mSnapshot; - } - } - /** - * Called when a task need a starting window. + * Called when a task need a splash screen starting window. */ - public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { - final PreferredStartingTypeHelper helper = - new PreferredStartingTypeHelper(windowInfo); - if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SPLASH_SCREEN) { - addSplashScreenStartingWindow(windowInfo, appToken); - } else if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SNAPSHOT) { - final TaskSnapshot snapshot = helper.mSnapshot; - makeTaskSnapshotWindow(windowInfo, appToken, snapshot); - } - // If prefer don't show, then don't show! - } - - private void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { + public void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { final RunningTaskInfo taskInfo = windowInfo.taskInfo; final ActivityInfo activityInfo = taskInfo.topActivityInfo; if (activityInfo == null) { @@ -378,8 +268,8 @@ public class StartingSurfaceDrawer { /** * Called when a task need a snapshot starting window. */ - private void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, - IBinder appToken, TaskSnapshot snapshot) { + void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken, + TaskSnapshot snapshot) { final int taskId = startingWindowInfo.taskInfo.taskId; final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, snapshot, mMainExecutor, () -> removeWindowSynced(taskId) /* clearWindow */); 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 new file mode 100644 index 000000000000..73bf8ac90c29 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -0,0 +1,196 @@ +/* + * 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.startingsurface; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; +import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT; +import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; + +import android.app.ActivityTaskManager; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.window.StartingWindowInfo; +import android.window.TaskOrganizer; +import android.window.TaskSnapshot; + +import com.android.wm.shell.common.ShellExecutor; + +/** + * Implementation to draw the starting window to an application, and remove the starting window + * until the application displays its own window. + * + * When receive {@link TaskOrganizer#addStartingWindow} callback, use this class to create a + * starting window and attached to the Task, then when the Task want to remove the starting window, + * the TaskOrganizer will receive {@link TaskOrganizer#removeStartingWindow} callback then use this + * class to remove the starting window of the Task. + * @hide + */ +public class StartingWindowController { + private static final String TAG = StartingWindowController.class.getSimpleName(); + static final boolean DEBUG_SPLASH_SCREEN = false; + static final boolean DEBUG_TASK_SNAPSHOT = false; + + private final StartingSurfaceDrawer mStartingSurfaceDrawer; + private final StartingTypeChecker mStartingTypeChecker = new StartingTypeChecker(); + private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl(); + + public StartingWindowController(Context context, ShellExecutor mainExecutor) { + mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor); + } + + /** + * Provide the implementation for Shell Module. + */ + public StartingSurface asStartingSurface() { + return mImpl; + } + + private static class StartingTypeChecker { + TaskSnapshot mSnapshot; + + StartingTypeChecker() { } + + private void reset() { + mSnapshot = null; + } + + private @StartingWindowInfo.StartingWindowType int + estimateStartingWindowType(StartingWindowInfo windowInfo) { + reset(); + final int parameter = windowInfo.startingWindowTypeParameter; + final boolean newTask = (parameter & TYPE_PARAMETER_NEW_TASK) != 0; + final boolean taskSwitch = (parameter & TYPE_PARAMETER_TASK_SWITCH) != 0; + final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0; + final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0; + final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0; + return estimateStartingWindowType(windowInfo, newTask, taskSwitch, + processRunning, allowTaskSnapshot, activityCreated); + } + + // reference from ActivityRecord#getStartingWindowType + private int estimateStartingWindowType(StartingWindowInfo windowInfo, + boolean newTask, boolean taskSwitch, boolean processRunning, + boolean allowTaskSnapshot, boolean activityCreated) { + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "preferredStartingWindowType newTask " + newTask + + " taskSwitch " + taskSwitch + + " processRunning " + processRunning + + " allowTaskSnapshot " + allowTaskSnapshot + + " activityCreated " + activityCreated); + } + if (newTask || !processRunning || (taskSwitch && !activityCreated)) { + return STARTING_WINDOW_TYPE_SPLASH_SCREEN; + } + if (taskSwitch && allowTaskSnapshot) { + final TaskSnapshot snapshot = getTaskSnapshot(windowInfo.taskInfo.taskId); + if (isSnapshotCompatible(windowInfo, snapshot)) { + return STARTING_WINDOW_TYPE_SNAPSHOT; + } + if (windowInfo.taskInfo.topActivityType != ACTIVITY_TYPE_HOME) { + return STARTING_WINDOW_TYPE_SPLASH_SCREEN; + } + } + return STARTING_WINDOW_TYPE_NONE; + } + + /** + * Returns {@code true} if the task snapshot is compatible with this activity (at least the + * rotation must be the same). + */ + private boolean isSnapshotCompatible(StartingWindowInfo windowInfo, TaskSnapshot snapshot) { + if (snapshot == null) { + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId); + } + return false; + } + + final int taskRotation = windowInfo.taskInfo.configuration + .windowConfiguration.getRotation(); + final int snapshotRotation = snapshot.getRotation(); + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "isSnapshotCompatible rotation " + taskRotation + + " snapshot " + snapshotRotation); + } + return taskRotation == snapshotRotation; + } + + private TaskSnapshot getTaskSnapshot(int taskId) { + if (mSnapshot != null) { + return mSnapshot; + } + try { + mSnapshot = ActivityTaskManager.getService().getTaskSnapshot(taskId, + false/* isLowResolution */); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get snapshot for task: " + taskId + ", from: " + e); + return null; + } + return mSnapshot; + } + } + + /** + * Called when a task need a starting window. + */ + void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { + final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo); + 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); + } + + /** + * Called when the content of a task is ready to show, starting window can be removed. + */ + void removeStartingWindow(int taskId) { + mStartingSurfaceDrawer.removeStartingWindow(taskId); + } + + private class StartingSurfaceImpl implements StartingSurface { + + @Override + public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { + StartingWindowController.this.addStartingWindow(windowInfo, appToken); + } + + @Override + public void removeStartingWindow(int taskId) { + StartingWindowController.this.removeStartingWindow(taskId); + } + + @Override + public void copySplashScreenView(int taskId) { + StartingWindowController.this.copySplashScreenView(taskId); + } + } +} 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 2182ee5590e1..629ff0db6b4a 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 @@ -97,8 +97,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - // Don't animate anything with an animating parent - if (change.getParent() != null) continue; + // Don't animate anything that isn't independent. + if (!TransitionInfo.isIndependent(change, info)) continue; Animation a = loadAnimation(info.getType(), change); if (a != null) { 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 89eee67bf5af..677db10d07a7 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 @@ -214,8 +214,8 @@ public class Transitions { final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); - // Don't move anything with an animating parent - if (change.getParent() != null) { + // Don't move anything that isn't independent within its parents + if (!TransitionInfo.isIndependent(change, info)) { if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); @@ -225,9 +225,13 @@ public class Transitions { continue; } - t.reparent(leash, info.getRootLeash()); - t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, - change.getStartAbsBounds().top - info.getRootOffset().y); + boolean hasParent = change.getParent() != null; + + if (!hasParent) { + t.reparent(leash, info.getRootLeash()); + t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, + change.getStartAbsBounds().top - info.getRootOffset().y); + } // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { t.show(leash); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index a0e9f43218f2..06b492dcb409 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -53,7 +53,6 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sizecompatui.SizeCompatUIController; -import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; import org.junit.Before; import org.junit.Test; @@ -79,8 +78,6 @@ public class ShellTaskOrganizerTests { private Context mContext; @Mock private SizeCompatUIController mSizeCompatUI; - @Mock - private StartingSurfaceDrawer mStartingSurfaceDrawer; ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); @@ -116,7 +113,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mSizeCompatUI, mStartingSurfaceDrawer)); + mSizeCompatUI)); } @Test 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 19ecc49513e5..c1c4c6dd08d7 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 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), + verify(mSplitScreenStarter).startIntent(any(), any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -217,12 +217,12 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), + verify(mSplitScreenStarter).startIntent(any(), 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(), + verify(mSplitScreenStarter).startIntent(any(), any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -234,12 +234,12 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), + verify(mSplitScreenStarter).startIntent(any(), 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(), + verify(mSplitScreenStarter).startIntent(any(), any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -251,7 +251,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), + verify(mSplitScreenStarter).startIntent(any(), any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -263,7 +263,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), + verify(mSplitScreenStarter).startIntent(any(), any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -276,13 +276,13 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), + verify(mSplitScreenStarter).startIntent(any(), 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(), + verify(mSplitScreenStarter).startIntent(any(), any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -295,13 +295,13 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), + verify(mSplitScreenStarter).startIntent(any(), 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(), + verify(mSplitScreenStarter).startIntent(any(), 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/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index d10c03677d30..195b701a1c26 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -43,7 +45,6 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PhonePipMenuController; @@ -125,7 +126,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void startSwipePipToHome_updatesOverrideMinSize() { - final Size minSize = new Size(100, 80); + final Size minSize = new Size(400, 320); mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize), createPipParams(null)); @@ -153,7 +154,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void onTaskAppeared_updatesOverrideMinSize() { - final Size minSize = new Size(100, 80); + final Size minSize = new Size(400, 320); mSpiedPipTaskOrganizer.onTaskAppeared( createTaskInfo(mComponent1, createPipParams(null), minSize), @@ -163,11 +164,29 @@ public class PipTaskOrganizerTest extends ShellTestCase { } @Test - public void onTaskInfoChanged_updatesAspectRatioIfChanged() { + public void onTaskInfoChanged_notInPip_deferUpdatesAspectRatio() { + final Rational startAspectRatio = new Rational(2, 1); + final Rational newAspectRatio = new Rational(1, 2); + mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, + createPipParams(startAspectRatio)), null /* leash */); + + // It is in entering transition, should defer onTaskInfoChanged callback in this case. + mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1, + createPipParams(newAspectRatio))); + assertEquals(startAspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f); + + // Once the entering transition finishes, the new aspect ratio applies in a deferred manner + mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); + assertEquals(newAspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f); + } + + @Test + public void onTaskInfoChanged_inPip_updatesAspectRatioIfChanged() { final Rational startAspectRatio = new Rational(2, 1); final Rational newAspectRatio = new Rational(1, 2); mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(startAspectRatio)), null /* leash */); + mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1, createPipParams(newAspectRatio))); @@ -176,9 +195,10 @@ public class PipTaskOrganizerTest extends ShellTestCase { } @Test - public void onTaskInfoChanged_updatesLastPipComponentName() { + public void onTaskInfoChanged_inPip_updatesLastPipComponentName() { mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(null)), null /* leash */); + mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2, createPipParams(null))); @@ -187,11 +207,12 @@ public class PipTaskOrganizerTest extends ShellTestCase { } @Test - public void onTaskInfoChanged_updatesOverrideMinSize() { + public void onTaskInfoChanged_inPip_updatesOverrideMinSize() { mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(null)), null /* leash */); + mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); - final Size minSize = new Size(100, 80); + final Size minSize = new Size(400, 320); mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2, createPipParams(null), minSize)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 19930485047c..75ea4ac94257 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -106,6 +106,7 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, mMockPipTransitionController, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); + mPipTouchHandler.init(); mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper()); mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler()); mPipTouchHandler.setPipMotionHelper(mMotionHelper); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index de7d6c74bb06..b9af9ce8895d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -125,7 +125,7 @@ public class StartingSurfaceDrawerTests { final Handler mainLoop = new Handler(Looper.getMainLooper()); final StartingWindowInfo windowInfo = createWindowInfo(taskId, android.R.style.Theme); - mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder); + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder); waitHandlerIdle(mainLoop); verify(mStartingSurfaceDrawer).postAddWindow( eq(taskId), eq(mBinder), any(), any(), any(), any()); @@ -143,7 +143,7 @@ public class StartingSurfaceDrawerTests { final Handler mainLoop = new Handler(Looper.getMainLooper()); final StartingWindowInfo windowInfo = createWindowInfo(taskId, 0); - mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder); + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder); waitHandlerIdle(mainLoop); verify(mStartingSurfaceDrawer).postAddWindow( eq(taskId), eq(mBinder), any(), any(), any(), any()); diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 9c743cea592a..76366fce2aea 100755 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -156,7 +156,11 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_ std::move(loaded_idmap))); } -const std::string& ApkAssets::GetPath() const { +std::optional<std::string_view> ApkAssets::GetPath() const { + return assets_provider_->GetPath(); +} + +const std::string& ApkAssets::GetDebugName() const { return assets_provider_->GetDebugName(); } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 36bde5ccf267..c0ef7be8b673 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -116,8 +116,10 @@ void AssetManager2::BuildDynamicRefTable() { package_groups_.clear(); package_ids_.fill(0xff); - // A mapping from apk assets path to the runtime package id of its first loaded package. - std::unordered_map<std::string, uint8_t> apk_assets_package_ids; + // A mapping from path of apk assets that could be target packages of overlays to the runtime + // package id of its first loaded package. Overlays currently can only override resources in the + // first package in the target resource table. + std::unordered_map<std::string, uint8_t> target_assets_package_ids; // Overlay resources are not directly referenced by an application so their resource ids // can change throughout the application's lifetime. Assign overlay package ids last. @@ -140,8 +142,8 @@ void AssetManager2::BuildDynamicRefTable() { if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) { // The target package must precede the overlay package in the apk assets paths in order // to take effect. - auto iter = apk_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath())); - if (iter == apk_assets_package_ids.end()) { + auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath())); + if (iter == target_assets_package_ids.end()) { LOG(INFO) << "failed to find target package for overlay " << loaded_idmap->OverlayApkPath(); } else { @@ -205,7 +207,10 @@ void AssetManager2::BuildDynamicRefTable() { package_name, static_cast<uint8_t>(entry.package_id)); } - apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id)); + if (auto apk_assets_path = apk_assets->GetPath()) { + // Overlay target ApkAssets must have been created using path based load apis. + target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id)); + } } } @@ -227,7 +232,7 @@ void AssetManager2::DumpToLog() const { std::string list; for (const auto& apk_assets : apk_assets_) { - base::StringAppendF(&list, "%s,", apk_assets->GetPath().c_str()); + base::StringAppendF(&list, "%s,", apk_assets->GetDebugName().c_str()); } LOG(INFO) << "ApkAssets: " << list; @@ -383,8 +388,8 @@ void AssetManager2::SetConfiguration(const ResTable_config& configuration) { } } -std::set<std::string> AssetManager2::GetNonSystemOverlayPaths() const { - std::set<std::string> non_system_overlays; +std::set<const ApkAssets*> AssetManager2::GetNonSystemOverlays() const { + std::set<const ApkAssets*> non_system_overlays; for (const PackageGroup& package_group : package_groups_) { bool found_system_package = false; for (const ConfiguredPackage& package : package_group.packages_) { @@ -396,7 +401,7 @@ std::set<std::string> AssetManager2::GetNonSystemOverlayPaths() const { if (!found_system_package) { for (const ConfiguredOverlay& overlay : package_group.overlays_) { - non_system_overlays.insert(apk_assets_[overlay.cookie]->GetPath()); + non_system_overlays.insert(apk_assets_[overlay.cookie]); } } } @@ -408,7 +413,7 @@ base::expected<std::set<ResTable_config>, IOError> AssetManager2::GetResourceCon bool exclude_system, bool exclude_mipmap) const { ATRACE_NAME("AssetManager::GetResourceConfigurations"); const auto non_system_overlays = - (exclude_system) ? GetNonSystemOverlayPaths() : std::set<std::string>(); + (exclude_system) ? GetNonSystemOverlays() : std::set<const ApkAssets*>(); std::set<ResTable_config> configurations; for (const PackageGroup& package_group : package_groups_) { @@ -419,8 +424,8 @@ base::expected<std::set<ResTable_config>, IOError> AssetManager2::GetResourceCon } auto apk_assets = apk_assets_[package_group.cookies_[i]]; - if (exclude_system && apk_assets->IsOverlay() - && non_system_overlays.find(apk_assets->GetPath()) == non_system_overlays.end()) { + if (exclude_system && apk_assets->IsOverlay() && + non_system_overlays.find(apk_assets) == non_system_overlays.end()) { // Exclude overlays that target system resources. continue; } @@ -439,7 +444,7 @@ std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system, ATRACE_NAME("AssetManager::GetResourceLocales"); std::set<std::string> locales; const auto non_system_overlays = - (exclude_system) ? GetNonSystemOverlayPaths() : std::set<std::string>(); + (exclude_system) ? GetNonSystemOverlays() : std::set<const ApkAssets*>(); for (const PackageGroup& package_group : package_groups_) { for (size_t i = 0; i < package_group.packages_.size(); i++) { @@ -449,8 +454,8 @@ std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system, } auto apk_assets = apk_assets_[package_group.cookies_[i]]; - if (exclude_system && apk_assets->IsOverlay() - && non_system_overlays.find(apk_assets->GetPath()) == non_system_overlays.end()) { + if (exclude_system && apk_assets->IsOverlay() && + non_system_overlays.find(apk_assets) == non_system_overlays.end()) { // Exclude overlays that target system resources. continue; } @@ -491,7 +496,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con AssetDir::FileInfo info; info.setFileName(String8(name.data(), name.size())); info.setFileType(type); - info.setSourceName(String8(apk_assets->GetPath().c_str())); + info.setSourceName(String8(apk_assets->GetDebugName().c_str())); files->add(info); }; @@ -846,7 +851,7 @@ std::string AssetManager2::GetLastResourceResolution() const { } log_stream << "\n\t" << prefix->second << ": " << *step.package_name << " (" - << apk_assets_[step.cookie]->GetPath() << ")"; + << apk_assets_[step.cookie]->GetDebugName() << ")"; if (!step.config_name.isEmpty()) { log_stream << " -" << step.config_name; } @@ -1556,41 +1561,32 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& o) { std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map; // Determine which ApkAssets are loaded in both theme AssetManagers. - std::vector<const ApkAssets*> src_assets = o.asset_manager_->GetApkAssets(); + const auto src_assets = o.asset_manager_->GetApkAssets(); for (size_t i = 0; i < src_assets.size(); i++) { const ApkAssets* src_asset = src_assets[i]; - std::vector<const ApkAssets*> dest_assets = asset_manager_->GetApkAssets(); + const auto dest_assets = asset_manager_->GetApkAssets(); for (size_t j = 0; j < dest_assets.size(); j++) { const ApkAssets* dest_asset = dest_assets[j]; + if (src_asset != dest_asset) { + // ResourcesManager caches and reuses ApkAssets when the same apk must be present in + // multiple AssetManagers. Two ApkAssets point to the same version of the same resources + // if they are the same instance. + continue; + } - // Map the runtime package of the source apk asset to the destination apk asset. - if (src_asset->GetPath() == dest_asset->GetPath()) { - const auto& src_packages = src_asset->GetLoadedArsc()->GetPackages(); - const auto& dest_packages = dest_asset->GetLoadedArsc()->GetPackages(); - - SourceToDestinationRuntimePackageMap package_map; - - // The source and destination package should have the same number of packages loaded in - // the same order. - const size_t N = src_packages.size(); - CHECK(N == dest_packages.size()) - << " LoadedArsc " << src_asset->GetPath() << " differs number of packages."; - for (size_t p = 0; p < N; p++) { - auto& src_package = src_packages[p]; - auto& dest_package = dest_packages[p]; - CHECK(src_package->GetPackageName() == dest_package->GetPackageName()) - << " Package " << src_package->GetPackageName() << " differs in load order."; - - int src_package_id = o.asset_manager_->GetAssignedPackageId(src_package.get()); - int dest_package_id = asset_manager_->GetAssignedPackageId(dest_package.get()); - package_map[src_package_id] = dest_package_id; - } - - src_to_dest_asset_cookies.insert(std::make_pair(i, j)); - src_asset_cookie_id_map.insert(std::make_pair(i, package_map)); - break; + // Map the package ids of the asset in the source AssetManager to the package ids of the + // asset in th destination AssetManager. + SourceToDestinationRuntimePackageMap package_map; + for (const auto& loaded_package : src_asset->GetLoadedArsc()->GetPackages()) { + const int src_package_id = o.asset_manager_->GetAssignedPackageId(loaded_package.get()); + const int dest_package_id = asset_manager_->GetAssignedPackageId(loaded_package.get()); + package_map[src_package_id] = dest_package_id; } + + src_to_dest_asset_cookies.insert(std::make_pair(i, j)); + src_asset_cookie_id_map.insert(std::make_pair(i, package_map)); + break; } } diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp index f3c48f7f9fc8..0aaf0b359b60 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -261,6 +261,13 @@ std::optional<uint32_t> ZipAssetsProvider::GetCrc(std::string_view path) const { return entry.crc32; } +std::optional<std::string_view> ZipAssetsProvider::GetPath() const { + if (name_.GetPath() != nullptr) { + return *name_.GetPath(); + } + return {}; +} + const std::string& ZipAssetsProvider::GetDebugName() const { return name_.GetDebugName(); } @@ -318,6 +325,10 @@ bool DirectoryAssetsProvider::ForEachFile( return true; } +std::optional<std::string_view> DirectoryAssetsProvider::GetPath() const { + return dir_; +} + const std::string& DirectoryAssetsProvider::GetDebugName() const { return dir_; } @@ -336,13 +347,9 @@ MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& prima std::unique_ptr<AssetsProvider>&& secondary) : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)), secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) { - if (primary_->GetDebugName() == kEmptyDebugString) { - debug_name_ = secondary_->GetDebugName(); - } else if (secondary_->GetDebugName() == kEmptyDebugString) { - debug_name_ = primary_->GetDebugName(); - } else { - debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName(); - } + debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName(); + path_ = (primary_->GetDebugName() != kEmptyDebugString) ? primary_->GetPath() + : secondary_->GetPath(); } std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create( @@ -367,6 +374,10 @@ bool MultiAssetsProvider::ForEachFile(const std::string& root_path, return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f); } +std::optional<std::string_view> MultiAssetsProvider::GetPath() const { + return path_; +} + const std::string& MultiAssetsProvider::GetDebugName() const { return debug_name_; } @@ -394,6 +405,10 @@ bool EmptyAssetsProvider::ForEachFile( return true; } +std::optional<std::string_view> EmptyAssetsProvider::GetPath() const { + return {}; +} + const std::string& EmptyAssetsProvider::GetDebugName() const { const static std::string kEmpty = kEmptyDebugString; return kEmpty; diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index d0019ed6be30..6f88f41976cd 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -34,7 +34,6 @@ namespace android { // Holds an APK. class ApkAssets { public: - // Creates an ApkAssets from a path on device. static std::unique_ptr<ApkAssets> Load(const std::string& path, package_property_t flags = 0U); @@ -61,12 +60,11 @@ class ApkAssets { static std::unique_ptr<ApkAssets> LoadOverlay(const std::string& idmap_path, package_property_t flags = 0U); - // TODO(177101983): Remove all uses of GetPath for checking whether two ApkAssets are the same. - // With the introduction of ResourcesProviders, not all ApkAssets have paths. This could cause - // bugs when path is used for comparison because multiple ApkAssets could have the same "firendly - // name". Use pointer equality instead. ResourceManager caches and reuses ApkAssets so the - // same asset should have the same pointer. - const std::string& GetPath() const; + // Path to the contents of the ApkAssets on disk. The path could represent an APk, a directory, + // or some other file type. + std::optional<std::string_view> GetPath() const; + + const std::string& GetDebugName() const; const AssetsProvider* GetAssetsProvider() const { return assets_provider_.get(); diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 2255973f1039..119f531b8634 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -412,7 +412,7 @@ class AssetManager2 { void RebuildFilterList(); // Retrieves the APK paths of overlays that overlay non-system packages. - std::set<std::string> GetNonSystemOverlayPaths() const; + std::set<const ApkAssets*> GetNonSystemOverlays() const; // AssetManager2::GetBag(resid) wraps this function to track which resource ids have already // been seen while traversing bag parents. diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h index 6f16ff453905..63bbdcc9698d 100644 --- a/libs/androidfw/include/androidfw/AssetsProvider.h +++ b/libs/androidfw/include/androidfw/AssetsProvider.h @@ -48,6 +48,10 @@ struct AssetsProvider { virtual bool ForEachFile(const std::string& path, const std::function<void(const StringPiece&, FileType)>& f) const = 0; + // Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an + // APk, a directory, or some other file type. + WARN_UNUSED virtual std::optional<std::string_view> GetPath() const = 0; + // Retrieves a name that represents the interface. This may or may not be the path of the // interface source. WARN_UNUSED virtual const std::string& GetDebugName() const = 0; @@ -85,9 +89,9 @@ struct ZipAssetsProvider : public AssetsProvider { bool ForEachFile(const std::string& root_path, const std::function<void(const StringPiece&, FileType)>& f) const override; + WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; WARN_UNUSED bool IsUpToDate() const override; - WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const; ~ZipAssetsProvider() override = default; @@ -125,6 +129,7 @@ struct DirectoryAssetsProvider : public AssetsProvider { bool ForEachFile(const std::string& path, const std::function<void(const StringPiece&, FileType)>& f) const override; + WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; WARN_UNUSED bool IsUpToDate() const override; @@ -149,6 +154,7 @@ struct MultiAssetsProvider : public AssetsProvider { bool ForEachFile(const std::string& root_path, const std::function<void(const StringPiece&, FileType)>& f) const override; + WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; WARN_UNUSED bool IsUpToDate() const override; @@ -163,6 +169,7 @@ struct MultiAssetsProvider : public AssetsProvider { std::unique_ptr<AssetsProvider> primary_; std::unique_ptr<AssetsProvider> secondary_; + std::optional<std::string_view> path_; std::string debug_name_; }; @@ -173,6 +180,7 @@ struct EmptyAssetsProvider : public AssetsProvider { bool ForEachFile(const std::string& path, const std::function<void(const StringPiece&, FileType)>& f) const override; + WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; WARN_UNUSED bool IsUpToDate() const override; diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 77ceda91898e..d663c52b2c08 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -421,7 +421,6 @@ cc_defaults { "libstatspull", "libstatssocket", "libpdfium", - "libbinder_ndk", ], static_libs: [ "libgif", diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index 912d04c5d87d..e9b2f4a9bb3b 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -80,6 +80,10 @@ public: explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) { memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t)); set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID; + // The struct is zeroed by memset above. That also sets FrameInfoIndex::InputEventId to + // equal android::os::IInputConstants::INVALID_INPUT_EVENT_ID == 0. + // Therefore, we can skip setting the value for InputEventId here. If the value for + // INVALID_INPUT_EVENT_ID changes, this code would have to be updated, as well. set(FrameInfoIndex::FrameDeadline) = std::numeric_limits<int64_t>::max(); } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 609706e2e49d..5540e2da78d9 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -552,8 +552,8 @@ public: bool promotedToLayer() const { return mLayerProperties.mType == LayerType::None && fitsOnLayer() && - (mComputedFields.mNeedLayerForFunctors || - mLayerProperties.mImageFilter != nullptr || + (mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr || + !mLayerProperties.getStretchEffect().isEmpty() || (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 && mPrimitiveFields.mHasOverlappingRendering)); } diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp index 51cbc7592861..d4fd1053b17f 100644 --- a/libs/hwui/effects/StretchEffect.cpp +++ b/libs/hwui/effects/StretchEffect.cpp @@ -15,13 +15,195 @@ */ #include "StretchEffect.h" +#include <SkImageFilter.h> +#include <SkRefCnt.h> +#include <SkRuntimeEffect.h> +#include <SkString.h> +#include <SkSurface.h> +#include <include/effects/SkImageFilters.h> + +#include <memory> namespace android::uirenderer { -sk_sp<SkImageFilter> StretchEffect::getImageFilter() const { - // TODO: Implement & Cache - // Probably need to use mutable to achieve caching - return nullptr; +static const SkString stretchShader = SkString(R"( + uniform shader uContentTexture; + + // multiplier to apply to scale effect + uniform float uMaxStretchIntensity; + + // Maximum percentage to stretch beyond bounds of target + uniform float uStretchAffectedDist; + + // Distance stretched as a function of the normalized overscroll times + // scale intensity + uniform float uDistanceStretchedX; + uniform float uDistanceStretchedY; + uniform float uDistDiffX; + + // Difference between the peak stretch amount and overscroll amount normalized + uniform float uDistDiffY; + + // Horizontal offset represented as a ratio of pixels divided by the target width + uniform float uScrollX; + // Vertical offset represented as a ratio of pixels divided by the target height + uniform float uScrollY; + + // Normalized overscroll amount in the horizontal direction + uniform float uOverscrollX; + + // Normalized overscroll amount in the vertical direction + uniform float uOverscrollY; + uniform float viewportWidth; // target height in pixels + uniform float viewportHeight; // target width in pixels + + void computeOverscrollStart( + out float outPos, + float inPos, + float overscroll, + float uStretchAffectedDist, + float distanceStretched + ) { + float offsetPos = uStretchAffectedDist - inPos; + float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos); + float stretchIntensity = overscroll * posBasedVariation; + outPos = distanceStretched - (offsetPos / (1. + stretchIntensity)); + } + + void computeOverscrollEnd( + out float outPos, + float inPos, + float overscroll, + float reverseStretchDist, + float uStretchAffectedDist, + float distanceStretched + ) { + float offsetPos = inPos - reverseStretchDist; + float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos)); + float stretchIntensity = (-overscroll) * posBasedVariation; + outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity))); + } + + void computeOverscroll( + out float outPos, + float inPos, + float overscroll, + float uStretchAffectedDist, + float distanceStretched, + float distanceDiff + ) { + if (overscroll > 0) { + if (inPos <= uStretchAffectedDist) { + computeOverscrollStart( + outPos, + inPos, + overscroll, + uStretchAffectedDist, + distanceStretched + ); + } else if (inPos >= distanceStretched) { + outPos = distanceDiff + inPos; + } + } + if (overscroll < 0) { + float stretchAffectedDist = 1. - uStretchAffectedDist; + if (inPos >= stretchAffectedDist) { + computeOverscrollEnd( + outPos, + inPos, + overscroll, + stretchAffectedDist, + uStretchAffectedDist, + distanceStretched + ); + } else if (inPos < stretchAffectedDist) { + outPos = -distanceDiff + inPos; + } + } + } + + vec4 main(vec2 coord) { + // Normalize SKSL pixel coordinate into a unit vector + float inU = coord.x / viewportWidth; + float inV = coord.y / viewportHeight; + float outU; + float outV; + float stretchIntensity; + // Add the normalized scroll position within scrolling list + inU += uScrollX; + inV += uScrollY; + outU = inU; + outV = inV; + computeOverscroll( + outU, + inU, + uOverscrollX, + uStretchAffectedDist, + uDistanceStretchedX, + uDistDiffX + ); + computeOverscroll( + outV, + inV, + uOverscrollY, + uStretchAffectedDist, + uDistanceStretchedY, + uDistDiffY + ); + coord.x = outU * viewportWidth; + coord.y = outV * viewportHeight; + return sample(uContentTexture, coord); + })"); + +static const float ZERO = 0.f; + +sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapshotImage) const { + if (isEmpty()) { + return nullptr; + } + + if (mStretchFilter != nullptr) { + return mStretchFilter; + } + + float distanceNotStretchedX = maxStretchAmount / stretchArea.width(); + float distanceNotStretchedY = maxStretchAmount / stretchArea.height(); + float normOverScrollDistX = mStretchDirection.x(); + float normOverScrollDistY = mStretchDirection.y(); + float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX)); + float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY)); + float diffX = distanceStretchedX - distanceNotStretchedX; + float diffY = distanceStretchedY - distanceNotStretchedY; + float viewportWidth = stretchArea.width(); + float viewportHeight = stretchArea.height(); + + if (mBuilder == nullptr) { + mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect()); + } + + mBuilder->child("uContentTexture") = snapshotImage->makeShader( + SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear)); + mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1); + mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1); + mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1); + mBuilder->uniform("uDistDiffX").set(&diffX, 1); + mBuilder->uniform("uDistDiffY").set(&diffY, 1); + mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1); + mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1); + mBuilder->uniform("uScrollX").set(&ZERO, 1); + mBuilder->uniform("uScrollY").set(&ZERO, 1); + mBuilder->uniform("viewportWidth").set(&viewportWidth, 1); + mBuilder->uniform("viewportHeight").set(&viewportHeight, 1); + + mStretchFilter = SkImageFilters::Shader(mBuilder->makeShader(nullptr, false), + SkRect{0, 0, viewportWidth, viewportHeight}); + + return mStretchFilter; +} + +sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() { + const static SkRuntimeEffect::Result instance = SkRuntimeEffect::Make(stretchShader); + return instance.effect; } } // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h index 7dfd6398765a..d2da06b31f68 100644 --- a/libs/hwui/effects/StretchEffect.h +++ b/libs/hwui/effects/StretchEffect.h @@ -18,9 +18,11 @@ #include "utils/MathUtils.h" +#include <SkImage.h> +#include <SkImageFilter.h> #include <SkPoint.h> #include <SkRect.h> -#include <SkImageFilter.h> +#include <SkRuntimeEffect.h> namespace android::uirenderer { @@ -31,15 +33,27 @@ public: SmoothStep, }; + StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmount) + : stretchArea(area), maxStretchAmount(maxStretchAmount), mStretchDirection(direction) {} + + StretchEffect() {} + bool isEmpty() const { - return MathUtils::isZero(stretchDirection.x()) - && MathUtils::isZero(stretchDirection.y()); + return MathUtils::isZero(mStretchDirection.x()) && MathUtils::isZero(mStretchDirection.y()); } void setEmpty() { *this = StretchEffect{}; } + StretchEffect& operator=(const StretchEffect& other) { + this->stretchArea = other.stretchArea; + this->mStretchDirection = other.mStretchDirection; + this->mStretchFilter = nullptr; + this->maxStretchAmount = other.maxStretchAmount; + return *this; + } + void mergeWith(const StretchEffect& other) { if (other.isEmpty()) { return; @@ -48,7 +62,7 @@ public: *this = other; return; } - stretchDirection += other.stretchDirection; + setStretchDirection(mStretchDirection + other.mStretchDirection); if (isEmpty()) { return setEmpty(); } @@ -56,11 +70,23 @@ public: maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount); } - sk_sp<SkImageFilter> getImageFilter() const; + sk_sp<SkImageFilter> getImageFilter(const sk_sp<SkImage>& snapshotImage) const; SkRect stretchArea {0, 0, 0, 0}; - SkVector stretchDirection {0, 0}; float maxStretchAmount = 0; + + void setStretchDirection(const SkVector& direction) { + mStretchFilter = nullptr; + mStretchDirection = direction; + } + + const SkVector getStretchDirection() const { return mStretchDirection; } + +private: + static sk_sp<SkRuntimeEffect> getStretchEffect(); + mutable SkVector mStretchDirection{0, 0}; + mutable std::unique_ptr<SkRuntimeShaderBuilder> mBuilder; + mutable sk_sp<SkImageFilter> mStretchFilter; }; } // namespace android::uirenderer diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 5f60437b5cc7..fc7d0d181949 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -180,14 +180,13 @@ static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA j } static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, - jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) { + jfloat left, jfloat top, jfloat right, + jfloat bottom, jfloat vX, jfloat vY, jfloat max) { + StretchEffect effect = + StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), {.fX = vX, .fY = vY}, max); RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith( - StretchEffect{ - .stretchArea = SkRect::MakeLTRB(left, top, right, bottom), - .stretchDirection = {.fX = vX, .fY = vY}, - .maxStretchAmount = max - }); + effect); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); return true; } @@ -659,10 +658,11 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, return; } #ifdef __ANDROID__ // Layoutlib does not support CanvasContext + SkVector stretchDirection = effect->getStretchDirection(); env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod, info.canvasContext.getFrameNumber(), area.left, area.top, - area.right, area.bottom, effect->stretchDirection.fX, - effect->stretchDirection.fY, effect->maxStretchAmount); + area.right, area.bottom, stretchDirection.fX, stretchDirection.fY, + effect->maxStretchAmount); #endif env->DeleteLocalRef(localref); } @@ -702,106 +702,110 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, const char* const kClassPathName = "android/graphics/RenderNode"; static const JNINativeMethod gMethods[] = { -// ---------------------------------------------------------------------------- -// Regular JNI -// ---------------------------------------------------------------------------- - { "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create }, - { "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer }, - { "nOutput", "(J)V", (void*) android_view_RenderNode_output }, - { "nGetUsageSize", "(J)I", (void*) android_view_RenderNode_getUsageSize }, - { "nGetAllocatedSize", "(J)I", (void*) android_view_RenderNode_getAllocatedSize }, - { "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator }, - { "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators }, - { "nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", (void*) android_view_RenderNode_requestPositionUpdates }, - -// ---------------------------------------------------------------------------- -// Critical JNI via @CriticalNative annotation in RenderNode.java -// ---------------------------------------------------------------------------- - { "nDiscardDisplayList", "(J)V", (void*) android_view_RenderNode_discardDisplayList }, - { "nIsValid", "(J)Z", (void*) android_view_RenderNode_isValid }, - { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType }, - { "nGetLayerType", "(J)I", (void*) android_view_RenderNode_getLayerType }, - { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint }, - { "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix }, - { "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix }, - { "nGetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_getAnimationMatrix }, - { "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds }, - { "nGetClipToBounds", "(J)Z", (void*) android_view_RenderNode_getClipToBounds }, - { "nSetClipBounds", "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds }, - { "nSetClipBoundsEmpty", "(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty }, - { "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards }, - { "nSetProjectionReceiver","(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver }, - - { "nSetOutlineRoundRect", "(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect }, - { "nSetOutlinePath", "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath }, - { "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty }, - { "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone }, - { "nClearStretch", "(J)Z", (void*) android_view_RenderNode_clearStretch }, - { "nStretch", "(JFFFFFFF)Z", (void*) android_view_RenderNode_stretch }, - { "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow }, - { "nSetSpotShadowColor", "(JI)Z", (void*) android_view_RenderNode_setSpotShadowColor }, - { "nGetSpotShadowColor", "(J)I", (void*) android_view_RenderNode_getSpotShadowColor }, - { "nSetAmbientShadowColor","(JI)Z", (void*) android_view_RenderNode_setAmbientShadowColor }, - { "nGetAmbientShadowColor","(J)I", (void*) android_view_RenderNode_getAmbientShadowColor }, - { "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline }, - { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip }, - - { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha }, - { "nSetRenderEffect", "(JJ)Z", (void*) android_view_RenderNode_setRenderEffect }, - { "nSetHasOverlappingRendering", "(JZ)Z", - (void*) android_view_RenderNode_setHasOverlappingRendering }, - { "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint }, - { "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation }, - { "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX }, - { "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY }, - { "nSetTranslationZ", "(JF)Z", (void*) android_view_RenderNode_setTranslationZ }, - { "nSetRotation", "(JF)Z", (void*) android_view_RenderNode_setRotation }, - { "nSetRotationX", "(JF)Z", (void*) android_view_RenderNode_setRotationX }, - { "nSetRotationY", "(JF)Z", (void*) android_view_RenderNode_setRotationY }, - { "nSetScaleX", "(JF)Z", (void*) android_view_RenderNode_setScaleX }, - { "nSetScaleY", "(JF)Z", (void*) android_view_RenderNode_setScaleY }, - { "nSetPivotX", "(JF)Z", (void*) android_view_RenderNode_setPivotX }, - { "nSetPivotY", "(JF)Z", (void*) android_view_RenderNode_setPivotY }, - { "nResetPivot", "(J)Z", (void*) android_view_RenderNode_resetPivot }, - { "nSetCameraDistance", "(JF)Z", (void*) android_view_RenderNode_setCameraDistance }, - { "nSetLeft", "(JI)Z", (void*) android_view_RenderNode_setLeft }, - { "nSetTop", "(JI)Z", (void*) android_view_RenderNode_setTop }, - { "nSetRight", "(JI)Z", (void*) android_view_RenderNode_setRight }, - { "nSetBottom", "(JI)Z", (void*) android_view_RenderNode_setBottom }, - { "nGetLeft", "(J)I", (void*) android_view_RenderNode_getLeft }, - { "nGetTop", "(J)I", (void*) android_view_RenderNode_getTop }, - { "nGetRight", "(J)I", (void*) android_view_RenderNode_getRight }, - { "nGetBottom", "(J)I", (void*) android_view_RenderNode_getBottom }, - { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom }, - { "nOffsetLeftAndRight", "(JI)Z", (void*) android_view_RenderNode_offsetLeftAndRight }, - { "nOffsetTopAndBottom", "(JI)Z", (void*) android_view_RenderNode_offsetTopAndBottom }, - - { "nHasOverlappingRendering", "(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering }, - { "nGetClipToOutline", "(J)Z", (void*) android_view_RenderNode_getClipToOutline }, - { "nGetAlpha", "(J)F", (void*) android_view_RenderNode_getAlpha }, - { "nGetCameraDistance", "(J)F", (void*) android_view_RenderNode_getCameraDistance }, - { "nGetScaleX", "(J)F", (void*) android_view_RenderNode_getScaleX }, - { "nGetScaleY", "(J)F", (void*) android_view_RenderNode_getScaleY }, - { "nGetElevation", "(J)F", (void*) android_view_RenderNode_getElevation }, - { "nGetTranslationX", "(J)F", (void*) android_view_RenderNode_getTranslationX }, - { "nGetTranslationY", "(J)F", (void*) android_view_RenderNode_getTranslationY }, - { "nGetTranslationZ", "(J)F", (void*) android_view_RenderNode_getTranslationZ }, - { "nGetRotation", "(J)F", (void*) android_view_RenderNode_getRotation }, - { "nGetRotationX", "(J)F", (void*) android_view_RenderNode_getRotationX }, - { "nGetRotationY", "(J)F", (void*) android_view_RenderNode_getRotationY }, - { "nIsPivotExplicitlySet", "(J)Z", (void*) android_view_RenderNode_isPivotExplicitlySet }, - { "nHasIdentityMatrix", "(J)Z", (void*) android_view_RenderNode_hasIdentityMatrix }, - - { "nGetTransformMatrix", "(JJ)V", (void*) android_view_RenderNode_getTransformMatrix }, - { "nGetInverseTransformMatrix","(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix }, - - { "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX }, - { "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY }, - { "nGetWidth", "(J)I", (void*) android_view_RenderNode_getWidth }, - { "nGetHeight", "(J)I", (void*) android_view_RenderNode_getHeight }, - { "nSetAllowForceDark", "(JZ)Z", (void*) android_view_RenderNode_setAllowForceDark }, - { "nGetAllowForceDark", "(J)Z", (void*) android_view_RenderNode_getAllowForceDark }, - { "nGetUniqueId", "(J)J", (void*) android_view_RenderNode_getUniqueId }, + // ---------------------------------------------------------------------------- + // Regular JNI + // ---------------------------------------------------------------------------- + {"nCreate", "(Ljava/lang/String;)J", (void*)android_view_RenderNode_create}, + {"nGetNativeFinalizer", "()J", (void*)android_view_RenderNode_getNativeFinalizer}, + {"nOutput", "(J)V", (void*)android_view_RenderNode_output}, + {"nGetUsageSize", "(J)I", (void*)android_view_RenderNode_getUsageSize}, + {"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize}, + {"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator}, + {"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators}, + {"nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", + (void*)android_view_RenderNode_requestPositionUpdates}, + + // ---------------------------------------------------------------------------- + // Critical JNI via @CriticalNative annotation in RenderNode.java + // ---------------------------------------------------------------------------- + {"nDiscardDisplayList", "(J)V", (void*)android_view_RenderNode_discardDisplayList}, + {"nIsValid", "(J)Z", (void*)android_view_RenderNode_isValid}, + {"nSetLayerType", "(JI)Z", (void*)android_view_RenderNode_setLayerType}, + {"nGetLayerType", "(J)I", (void*)android_view_RenderNode_getLayerType}, + {"nSetLayerPaint", "(JJ)Z", (void*)android_view_RenderNode_setLayerPaint}, + {"nSetStaticMatrix", "(JJ)Z", (void*)android_view_RenderNode_setStaticMatrix}, + {"nSetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_setAnimationMatrix}, + {"nGetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_getAnimationMatrix}, + {"nSetClipToBounds", "(JZ)Z", (void*)android_view_RenderNode_setClipToBounds}, + {"nGetClipToBounds", "(J)Z", (void*)android_view_RenderNode_getClipToBounds}, + {"nSetClipBounds", "(JIIII)Z", (void*)android_view_RenderNode_setClipBounds}, + {"nSetClipBoundsEmpty", "(J)Z", (void*)android_view_RenderNode_setClipBoundsEmpty}, + {"nSetProjectBackwards", "(JZ)Z", (void*)android_view_RenderNode_setProjectBackwards}, + {"nSetProjectionReceiver", "(JZ)Z", (void*)android_view_RenderNode_setProjectionReceiver}, + + {"nSetOutlineRoundRect", "(JIIIIFF)Z", (void*)android_view_RenderNode_setOutlineRoundRect}, + {"nSetOutlinePath", "(JJF)Z", (void*)android_view_RenderNode_setOutlinePath}, + {"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty}, + {"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone}, + {"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch}, + {"nStretch", "(JFFFFFFF)Z", (void*)android_view_RenderNode_stretch}, + {"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow}, + {"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor}, + {"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor}, + {"nSetAmbientShadowColor", "(JI)Z", (void*)android_view_RenderNode_setAmbientShadowColor}, + {"nGetAmbientShadowColor", "(J)I", (void*)android_view_RenderNode_getAmbientShadowColor}, + {"nSetClipToOutline", "(JZ)Z", (void*)android_view_RenderNode_setClipToOutline}, + {"nSetRevealClip", "(JZFFF)Z", (void*)android_view_RenderNode_setRevealClip}, + + {"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha}, + {"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect}, + {"nSetHasOverlappingRendering", "(JZ)Z", + (void*)android_view_RenderNode_setHasOverlappingRendering}, + {"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint}, + {"nSetElevation", "(JF)Z", (void*)android_view_RenderNode_setElevation}, + {"nSetTranslationX", "(JF)Z", (void*)android_view_RenderNode_setTranslationX}, + {"nSetTranslationY", "(JF)Z", (void*)android_view_RenderNode_setTranslationY}, + {"nSetTranslationZ", "(JF)Z", (void*)android_view_RenderNode_setTranslationZ}, + {"nSetRotation", "(JF)Z", (void*)android_view_RenderNode_setRotation}, + {"nSetRotationX", "(JF)Z", (void*)android_view_RenderNode_setRotationX}, + {"nSetRotationY", "(JF)Z", (void*)android_view_RenderNode_setRotationY}, + {"nSetScaleX", "(JF)Z", (void*)android_view_RenderNode_setScaleX}, + {"nSetScaleY", "(JF)Z", (void*)android_view_RenderNode_setScaleY}, + {"nSetPivotX", "(JF)Z", (void*)android_view_RenderNode_setPivotX}, + {"nSetPivotY", "(JF)Z", (void*)android_view_RenderNode_setPivotY}, + {"nResetPivot", "(J)Z", (void*)android_view_RenderNode_resetPivot}, + {"nSetCameraDistance", "(JF)Z", (void*)android_view_RenderNode_setCameraDistance}, + {"nSetLeft", "(JI)Z", (void*)android_view_RenderNode_setLeft}, + {"nSetTop", "(JI)Z", (void*)android_view_RenderNode_setTop}, + {"nSetRight", "(JI)Z", (void*)android_view_RenderNode_setRight}, + {"nSetBottom", "(JI)Z", (void*)android_view_RenderNode_setBottom}, + {"nGetLeft", "(J)I", (void*)android_view_RenderNode_getLeft}, + {"nGetTop", "(J)I", (void*)android_view_RenderNode_getTop}, + {"nGetRight", "(J)I", (void*)android_view_RenderNode_getRight}, + {"nGetBottom", "(J)I", (void*)android_view_RenderNode_getBottom}, + {"nSetLeftTopRightBottom", "(JIIII)Z", + (void*)android_view_RenderNode_setLeftTopRightBottom}, + {"nOffsetLeftAndRight", "(JI)Z", (void*)android_view_RenderNode_offsetLeftAndRight}, + {"nOffsetTopAndBottom", "(JI)Z", (void*)android_view_RenderNode_offsetTopAndBottom}, + + {"nHasOverlappingRendering", "(J)Z", + (void*)android_view_RenderNode_hasOverlappingRendering}, + {"nGetClipToOutline", "(J)Z", (void*)android_view_RenderNode_getClipToOutline}, + {"nGetAlpha", "(J)F", (void*)android_view_RenderNode_getAlpha}, + {"nGetCameraDistance", "(J)F", (void*)android_view_RenderNode_getCameraDistance}, + {"nGetScaleX", "(J)F", (void*)android_view_RenderNode_getScaleX}, + {"nGetScaleY", "(J)F", (void*)android_view_RenderNode_getScaleY}, + {"nGetElevation", "(J)F", (void*)android_view_RenderNode_getElevation}, + {"nGetTranslationX", "(J)F", (void*)android_view_RenderNode_getTranslationX}, + {"nGetTranslationY", "(J)F", (void*)android_view_RenderNode_getTranslationY}, + {"nGetTranslationZ", "(J)F", (void*)android_view_RenderNode_getTranslationZ}, + {"nGetRotation", "(J)F", (void*)android_view_RenderNode_getRotation}, + {"nGetRotationX", "(J)F", (void*)android_view_RenderNode_getRotationX}, + {"nGetRotationY", "(J)F", (void*)android_view_RenderNode_getRotationY}, + {"nIsPivotExplicitlySet", "(J)Z", (void*)android_view_RenderNode_isPivotExplicitlySet}, + {"nHasIdentityMatrix", "(J)Z", (void*)android_view_RenderNode_hasIdentityMatrix}, + + {"nGetTransformMatrix", "(JJ)V", (void*)android_view_RenderNode_getTransformMatrix}, + {"nGetInverseTransformMatrix", "(JJ)V", + (void*)android_view_RenderNode_getInverseTransformMatrix}, + + {"nGetPivotX", "(J)F", (void*)android_view_RenderNode_getPivotX}, + {"nGetPivotY", "(J)F", (void*)android_view_RenderNode_getPivotY}, + {"nGetWidth", "(J)I", (void*)android_view_RenderNode_getWidth}, + {"nGetHeight", "(J)I", (void*)android_view_RenderNode_getHeight}, + {"nSetAllowForceDark", "(JZ)Z", (void*)android_view_RenderNode_setAllowForceDark}, + {"nGetAllowForceDark", "(J)Z", (void*)android_view_RenderNode_getAllowForceDark}, + {"nGetUniqueId", "(J)J", (void*)android_view_RenderNode_getUniqueId}, }; int register_android_view_RenderNode(JNIEnv* env) { diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index c01021221f37..cb0ff8d871d4 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -169,8 +169,8 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const { displayList->mProjectedOutline = nullptr; } -static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier, - SkPaint* paint) { +static bool layerNeedsPaint(const sk_sp<SkImage>& snapshotImage, const LayerProperties& properties, + float alphaMultiplier, SkPaint* paint) { if (alphaMultiplier < 1.0f || properties.alpha() < 255 || properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr || properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) { @@ -179,7 +179,8 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip paint->setColorFilter(sk_ref_sp(properties.getColorFilter())); sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter()); - sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter(); + sk_sp<SkImageFilter> stretchFilter = + properties.getStretchEffect().getImageFilter(snapshotImage); sk_sp<SkImageFilter> filter; if (imageFilter && stretchFilter) { filter = SkImageFilters::Compose( @@ -240,7 +241,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { if (renderNode->getLayerSurface() && mComposeLayer) { SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer); SkPaint paint; - layerNeedsPaint(layerProperties, alphaMultiplier, &paint); + sk_sp<SkImage> snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot(); + layerNeedsPaint(snapshotImage, layerProperties, alphaMultiplier, &paint); SkSamplingOptions sampling(SkFilterMode::kLinear); // surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so @@ -254,8 +256,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { canvas->drawAnnotation(bounds, String8::format( "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr); } - canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds, - bounds, sampling, &paint, SkCanvas::kStrict_SrcRectConstraint); + canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint, + SkCanvas::kStrict_SrcRectConstraint); if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) { renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index af7271e96cb9..61f9960c4d8d 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -176,9 +176,6 @@ void SkiaRecordingCanvas::FilterForImage(SkPaint& paint) { if (sApiLevel <= 27 && paint.getBlendMode() == SkBlendMode::kClear) { paint.setBlendMode(SkBlendMode::kDstOut); } - - // disabling AA on bitmap draws matches legacy HWUI behavior - paint.setAntiAlias(false); } static SkFilterMode Paint_to_filter(const SkPaint& paint) { diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index c9146b2fc2d1..3408ffda3f9d 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -17,7 +17,7 @@ #include "DrawFrameTask.h" #include <utils/Log.h> -#include <utils/Trace.h> +#include <utils/TraceUtils.h> #include "../DeferredLayerUpdater.h" #include "../DisplayList.h" @@ -82,7 +82,8 @@ void DrawFrameTask::postAndWait() { } void DrawFrameTask::run() { - ATRACE_NAME("DrawFrame"); + const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)]; + ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId); bool canUnblockUiThread; bool canDrawThisFrame; diff --git a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl index cee3635a1e3b..8186fb741b59 100644 --- a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl @@ -32,8 +32,8 @@ parcelable SoundModel { * Unique vendor ID. Identifies the engine the sound model * was build for */ String vendorUuid; - /** Opaque data transparent to Android framework */ - ParcelFileDescriptor data; + /** Opaque data transparent to Android framework. May be null if dataSize is 0. */ + @nullable ParcelFileDescriptor data; /** Size of the above data, in bytes. */ int dataSize; } diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java index ff9fd4187272..ca175b4853a6 100644 --- a/media/java/android/media/AudioMetadata.java +++ b/media/java/android/media/AudioMetadata.java @@ -195,6 +195,61 @@ public final class AudioMetadata { @NonNull public static final Key<Integer> KEY_AUDIO_ENCODING = createKey("audio-encoding", Integer.class); + + /** + * A key representing the audio presentation id being decoded by a next generation + * audio decoder. + * + * An Integer value representing presentation id. + * + * @see AudioPresentation#getPresentationId() + */ + @NonNull public static final Key<Integer> KEY_PRESENTATION_ID = + createKey("presentation-id", Integer.class); + + /** + * A key representing the audio program id being decoded by a next generation + * audio decoder. + * + * An Integer value representing program id. + * + * @see AudioPresentation#getProgramId() + */ + @NonNull public static final Key<Integer> KEY_PROGRAM_ID = + createKey("program-id", Integer.class); + + + /** + * A key representing the audio presentation content classifier being rendered + * by a next generation audio decoder. + * + * An Integer value representing presentation content classifier. + * + * @see AudioPresentation.ContentClassifier + * One of {@link AudioPresentation#CONTENT_UNKNOWN}, + * {@link AudioPresentation#CONTENT_MAIN}, + * {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, + * {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, + * {@link AudioPresentation#CONTENT_HEARING_IMPAIRED}, + * {@link AudioPresentation#CONTENT_DIALOG}, + * {@link AudioPresentation#CONTENT_COMMENTARY}, + * {@link AudioPresentation#CONTENT_EMERGENCY}, + * {@link AudioPresentation#CONTENT_VOICEOVER}. + */ + @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER = + createKey("presentation-content-classifier", Integer.class); + + /** + * A key representing the audio presentation language being rendered by a next + * generation audio decoder. + * + * A String value representing ISO 639-2 (three letter code). + * + * @see AudioPresentation#getLocale() + */ + @NonNull public static final Key<String> KEY_PRESENTATION_LANGUAGE = + createKey("presentation-language", String.class); + private Format() {} // delete constructor } diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java index 894fbba6c983..47358be3e926 100644 --- a/media/java/android/media/AudioPresentation.java +++ b/media/java/android/media/AudioPresentation.java @@ -57,6 +57,64 @@ public final class AudioPresentation { /** @hide */ @IntDef( value = { + CONTENT_UNKNOWN, + CONTENT_MAIN, + CONTENT_MUSIC_AND_EFFECTS, + CONTENT_VISUALLY_IMPAIRED, + CONTENT_HEARING_IMPAIRED, + CONTENT_DIALOG, + CONTENT_COMMENTARY, + CONTENT_EMERGENCY, + CONTENT_VOICEOVER, + }) + + /** + * The ContentClassifier int definitions represent the AudioPresentation content + * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1) + */ + @Retention(RetentionPolicy.SOURCE) + public @interface ContentClassifier {} + + /** + * Audio presentation classifier: Unknown. + */ + public static final int CONTENT_UNKNOWN = -1; + /** + * Audio presentation classifier: Complete main. + */ + public static final int CONTENT_MAIN = 0; + /** + * Audio presentation content classifier: Music and effects. + */ + public static final int CONTENT_MUSIC_AND_EFFECTS = 1; + /** + * Audio presentation content classifier: Visually impaired. + */ + public static final int CONTENT_VISUALLY_IMPAIRED = 2; + /** + * Audio presentation content classifier: Hearing impaired. + */ + public static final int CONTENT_HEARING_IMPAIRED = 3; + /** + * Audio presentation content classifier: Dialog. + */ + public static final int CONTENT_DIALOG = 4; + /** + * Audio presentation content classifier: Commentary. + */ + public static final int CONTENT_COMMENTARY = 5; + /** + * Audio presentation content classifier: Emergency. + */ + public static final int CONTENT_EMERGENCY = 6; + /** + * Audio presentation content classifier: Voice over. + */ + public static final int CONTENT_VOICEOVER = 7; + + /** @hide */ + @IntDef( + value = { MASTERING_NOT_INDICATED, MASTERED_FOR_STEREO, MASTERED_FOR_SURROUND, diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 04eaf07d7924..88731d25706c 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -929,12 +929,21 @@ public class AudioTrack extends PlayerBase private final int mSyncId; /** + * A special content id for {@link #TunerConfiguration(int, int)} + * indicating audio is delivered + * from an {@code AudioTrack} write, not tunneled from the tuner stack. + */ + public static final int CONTENT_ID_NONE = 0; + + /** * Constructs a TunerConfiguration instance for use in {@link AudioTrack.Builder} * * @param contentId selects the audio stream to use. * The contentId may be obtained from - * {@link android.media.tv.tuner.filter.Filter#getId()}. - * This is always a positive number. + * {@link android.media.tv.tuner.filter.Filter#getId()}, + * such obtained id is always a positive number. + * If audio is to be delivered through an {@code AudioTrack} write + * then {@code CONTENT_ID_NONE} may be used. * @param syncId selects the clock to use for synchronization * of audio with other streams such as video. * The syncId may be obtained from @@ -943,10 +952,10 @@ public class AudioTrack extends PlayerBase */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public TunerConfiguration( - @IntRange(from = 1) int contentId, @IntRange(from = 1)int syncId) { - if (contentId < 1) { + @IntRange(from = 0) int contentId, @IntRange(from = 1)int syncId) { + if (contentId < 0) { throw new IllegalArgumentException( - "contentId " + contentId + " must be positive"); + "contentId " + contentId + " must be positive or CONTENT_ID_NONE"); } if (syncId < 1) { throw new IllegalArgumentException("syncId " + syncId + " must be positive"); diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index f5b204a4a908..5656dffe1d4a 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -16,6 +16,7 @@ package android.media; +import android.annotation.CallbackExecutor; import android.annotation.IntRange; import android.annotation.NonNull; import android.graphics.GraphicBuffer; @@ -24,6 +25,7 @@ import android.graphics.ImageFormat.Format; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.hardware.HardwareBuffer.Usage; +import android.hardware.camera2.MultiResolutionImageReader; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -36,7 +38,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.NioUtils; import java.util.List; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -134,7 +138,8 @@ public class ImageReader implements AutoCloseable { // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not // work, and is inscrutable anyway return new ImageReader(width, height, format, maxImages, - format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN); + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, + /*parent*/ null); } /** @@ -250,18 +255,37 @@ public class ImageReader implements AutoCloseable { // throw new IllegalArgumentException("The given format=" + Integer.toHexString(format) // + " & usage=" + Long.toHexString(usage) + " is not supported"); // } - return new ImageReader(width, height, format, maxImages, usage); + return new ImageReader(width, height, format, maxImages, usage, /*parent*/ null); } + /** + * @hide + */ + public static @NonNull ImageReader newInstance( + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @Format int format, + @IntRange(from = 1) int maxImages, + @NonNull MultiResolutionImageReader parent) { + // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not + // work, and is inscrutable anyway + return new ImageReader(width, height, format, maxImages, + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, + parent); + } + + /** * @hide */ - protected ImageReader(int width, int height, int format, int maxImages, long usage) { + protected ImageReader(int width, int height, int format, int maxImages, long usage, + MultiResolutionImageReader parent) { mWidth = width; mHeight = height; mFormat = format; mUsage = usage; mMaxImages = maxImages; + mParent = parent; if (width < 1 || height < 1) { throw new IllegalArgumentException( @@ -433,6 +457,9 @@ public class ImageReader implements AutoCloseable { if (image != null) { image.close(); } + if (mParent != null) { + mParent.flushOther(this); + } } } @@ -584,12 +611,38 @@ public class ImageReader implements AutoCloseable { } if (mListenerHandler == null || mListenerHandler.getLooper() != looper) { mListenerHandler = new ListenerHandler(looper); + mListenerExecutor = new HandlerExecutor(mListenerHandler); } - mListener = listener; } else { - mListener = null; mListenerHandler = null; + mListenerExecutor = null; } + mListener = listener; + } + } + + /** + * Register a listener to be invoked when a new image becomes available + * from the ImageReader. + * + * @param listener + * The listener that will be run. + * @param executor + * The executor which will be used to invoke the listener. + * @throws IllegalArgumentException + * If no handler specified and the calling thread has no looper. + * + * @hide + */ + public void setOnImageAvailableListenerWithExecutor(@NonNull OnImageAvailableListener listener, + @NonNull Executor executor) { + if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } + + synchronized (mListenerLock) { + mListenerExecutor = executor; + mListener = listener; } } @@ -763,12 +816,27 @@ public class ImageReader implements AutoCloseable { return; } - final Handler handler; + final Executor executor; + final OnImageAvailableListener listener; synchronized (ir.mListenerLock) { - handler = ir.mListenerHandler; + executor = ir.mListenerExecutor; + listener = ir.mListener; } - if (handler != null) { - handler.sendEmptyMessage(0); + final boolean isReaderValid; + synchronized (ir.mCloseLock) { + isReaderValid = ir.mIsReaderValid; + } + + // It's dangerous to fire onImageAvailable() callback when the ImageReader + // is being closed, as application could acquire next image in the + // onImageAvailable() callback. + if (executor != null && listener != null && isReaderValid) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.onImageAvailable(ir); + } + }); } } @@ -785,11 +853,16 @@ public class ImageReader implements AutoCloseable { private final Object mCloseLock = new Object(); private boolean mIsReaderValid = false; private OnImageAvailableListener mListener; + private Executor mListenerExecutor; private ListenerHandler mListenerHandler; // Keep track of the successfully acquired Images. This need to be thread safe as the images // could be closed by different threads (e.g., application thread and GC thread). private List<Image> mAcquiredImages = new CopyOnWriteArrayList<>(); + // Applicable if this isn't a standalone ImageReader, but belongs to a + // MultiResolutionImageReader. + private final MultiResolutionImageReader mParent; + /** * This field is used by native code, do not access or modify. */ @@ -802,23 +875,22 @@ public class ImageReader implements AutoCloseable { public ListenerHandler(Looper looper) { super(looper, null, true /*async*/); } + } - @Override - public void handleMessage(Message msg) { - OnImageAvailableListener listener; - synchronized (mListenerLock) { - listener = mListener; - } + /** + * An adapter {@link Executor} that posts all executed tasks onto the + * given {@link Handler}. + **/ + private final class HandlerExecutor implements Executor { + private final Handler mHandler; + + public HandlerExecutor(@NonNull Handler handler) { + mHandler = Objects.requireNonNull(handler); + } - // It's dangerous to fire onImageAvailable() callback when the ImageReader is being - // closed, as application could acquire next image in the onImageAvailable() callback. - boolean isReaderValid = false; - synchronized (mCloseLock) { - isReaderValid = mIsReaderValid; - } - if (listener != null && isReaderValid) { - listener.onImageAvailable(ImageReader.this); - } + @Override + public void execute(Runnable command) { + mHandler.post(command); } } diff --git a/media/java/android/media/metrics/Event.java b/media/java/android/media/metrics/Event.java index 5646dcdb6c9c..96b61d2eaf4d 100644 --- a/media/java/android/media/metrics/Event.java +++ b/media/java/android/media/metrics/Event.java @@ -17,22 +17,30 @@ package android.media.metrics; import android.annotation.IntRange; +import android.os.Bundle; /** * Abstract class for metrics events. */ public abstract class Event { - private final long mTimeSinceCreatedMillis; + final long mTimeSinceCreatedMillis; + Bundle mExtras; // hide default constructor /* package */ Event() { mTimeSinceCreatedMillis = MediaMetricsManager.INVALID_TIMESTAMP; } + // TODO: remove protected Event(long timeSinceCreatedMillis) { mTimeSinceCreatedMillis = timeSinceCreatedMillis; } + /* package */ Event(long timeSinceCreatedMillis, Bundle extras) { + mTimeSinceCreatedMillis = timeSinceCreatedMillis; + mExtras = extras; + } + /** * Gets time since the corresponding instance is created in millisecond. * @return the timestamp since the instance is created, or -1 if unknown. @@ -41,4 +49,9 @@ public abstract class Event { public long getTimeSinceCreatedMillis() { return mTimeSinceCreatedMillis; } + + /** @hide */ + public Bundle getExtras() { + return mExtras; + } } diff --git a/media/java/android/media/metrics/IMediaMetricsManager.aidl b/media/java/android/media/metrics/IMediaMetricsManager.aidl index 2cb2ab5c42b5..f2c0d44a00b1 100644 --- a/media/java/android/media/metrics/IMediaMetricsManager.aidl +++ b/media/java/android/media/metrics/IMediaMetricsManager.aidl @@ -28,7 +28,8 @@ import android.media.metrics.TrackChangeEvent; */ interface IMediaMetricsManager { void reportPlaybackMetrics(in String sessionId, in PlaybackMetrics metrics, int userId); - String getSessionId(int userId); + String getPlaybackSessionId(int userId); + String getRecordingSessionId(int userId); void reportNetworkEvent(in String sessionId, in NetworkEvent event, int userId); void reportPlaybackErrorEvent(in String sessionId, in PlaybackErrorEvent event, int userId); void reportPlaybackStateEvent(in String sessionId, in PlaybackStateEvent event, int userId); diff --git a/media/java/android/media/metrics/LogSessionId.java b/media/java/android/media/metrics/LogSessionId.java new file mode 100644 index 000000000000..7ddb259f7f28 --- /dev/null +++ b/media/java/android/media/metrics/LogSessionId.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.metrics; + +/** + * An instances of this class represents the ID of a log session. + * @hide + */ +public class LogSessionId { + private final String mSessionId; + + /* package */ LogSessionId(String id) { + mSessionId = id; + } + + /** @hide */ + public String getStringId() { + return mSessionId; + } +} diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java index de780f672b28..9710e88cf24b 100644 --- a/media/java/android/media/metrics/MediaMetricsManager.java +++ b/media/java/android/media/metrics/MediaMetricsManager.java @@ -94,7 +94,7 @@ public class MediaMetricsManager { @NonNull public PlaybackSession createPlaybackSession() { try { - String id = mService.getSessionId(mUserId); + String id = mService.getPlaybackSessionId(mUserId); PlaybackSession session = new PlaybackSession(id, this); return session; } catch (RemoteException e) { @@ -103,6 +103,21 @@ public class MediaMetricsManager { } /** + * Creates a recording session. + * @hide + */ + @NonNull + public RecordingSession createRecordingSession() { + try { + String id = mService.getRecordingSessionId(mUserId); + RecordingSession session = new RecordingSession(id, this); + return session; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Reports error event. * @hide */ diff --git a/media/java/android/media/metrics/NetworkEvent.java b/media/java/android/media/metrics/NetworkEvent.java index 029edeb93374..098885cc9bdd 100644 --- a/media/java/android/media/metrics/NetworkEvent.java +++ b/media/java/android/media/metrics/NetworkEvent.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -33,6 +34,9 @@ import java.util.Objects; public final class NetworkEvent extends Event implements Parcelable { /** Network type is not specified. Default type. */ public static final int NETWORK_TYPE_NONE = 0; + // TODO: replace NONE with UNKNOWN + /** @hide */ + public static final int NETWORK_TYPE_UNKNOWN = 0; /** Other network type */ public static final int NETWORK_TYPE_OTHER = 1; /** Wi-Fi network */ @@ -49,6 +53,9 @@ public final class NetworkEvent extends Event implements Parcelable { public static final int NETWORK_TYPE_5G_NSA = 7; /** 5G SA network */ public static final int NETWORK_TYPE_5G_SA = 8; + /** Not network connected */ + /** @hide */ + public static final int NETWORK_TYPE_OFFLINE = 9; private final int mNetworkType; private final long mTimeSinceCreatedMillis; @@ -56,6 +63,7 @@ public final class NetworkEvent extends Event implements Parcelable { /** @hide */ @IntDef(prefix = "NETWORK_TYPE_", value = { NETWORK_TYPE_NONE, + NETWORK_TYPE_UNKNOWN, NETWORK_TYPE_OTHER, NETWORK_TYPE_WIFI, NETWORK_TYPE_ETHERNET, @@ -63,7 +71,8 @@ public final class NetworkEvent extends Event implements Parcelable { NETWORK_TYPE_3G, NETWORK_TYPE_4G, NETWORK_TYPE_5G_NSA, - NETWORK_TYPE_5G_SA + NETWORK_TYPE_5G_SA, + NETWORK_TYPE_OFFLINE }) @Retention(RetentionPolicy.SOURCE) public @interface NetworkType {} @@ -92,6 +101,8 @@ public final class NetworkEvent extends Event implements Parcelable { return "NETWORK_TYPE_5G_NSA"; case NETWORK_TYPE_5G_SA: return "NETWORK_TYPE_5G_SA"; + case NETWORK_TYPE_OFFLINE: + return "NETWORK_TYPE_OFFLINE"; default: return Integer.toHexString(value); } @@ -102,9 +113,10 @@ public final class NetworkEvent extends Event implements Parcelable { * * @hide */ - public NetworkEvent(@NetworkType int type, long timeSinceCreatedMillis) { + public NetworkEvent(@NetworkType int type, long timeSinceCreatedMillis, Bundle extras) { this.mNetworkType = type; this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; + this.mExtras = extras.deepCopy(); } /** @@ -149,8 +161,12 @@ public final class NetworkEvent extends Event implements Parcelable { @Override public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + byte flg = 0; + if (mExtras != null) flg |= 0x1; + dest.writeByte(flg); dest.writeInt(mNetworkType); dest.writeLong(mTimeSinceCreatedMillis); + if (mExtras != null) dest.writeBundle(mExtras); } @Override @@ -160,11 +176,14 @@ public final class NetworkEvent extends Event implements Parcelable { /** @hide */ /* package-private */ NetworkEvent(@NonNull android.os.Parcel in) { + byte flg = in.readByte(); int type = in.readInt(); long timeSinceCreatedMillis = in.readLong(); + Bundle extras = (flg & 0x2) == 0 ? null : in.readBundle(); this.mNetworkType = type; this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; + this.mExtras = extras; } /** @@ -189,6 +208,7 @@ public final class NetworkEvent extends Event implements Parcelable { public static final class Builder { private int mNetworkType = NETWORK_TYPE_NONE; private long mTimeSinceCreatedMillis = -1; + private Bundle mExtras; /** * Creates a new Builder. @@ -214,9 +234,19 @@ public final class NetworkEvent extends Event implements Parcelable { return this; } + /** + * Set extras for compatibility. + * <p>Should be used by support library only. + * @hide + */ + public @NonNull Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + /** Builds the instance. */ public @NonNull NetworkEvent build() { - NetworkEvent o = new NetworkEvent(mNetworkType, mTimeSinceCreatedMillis); + NetworkEvent o = new NetworkEvent(mNetworkType, mTimeSinceCreatedMillis, mExtras); return o; } } diff --git a/media/java/android/media/metrics/PlaybackErrorEvent.java b/media/java/android/media/metrics/PlaybackErrorEvent.java index 5a0820d16cb9..b23b4d2728b4 100644 --- a/media/java/android/media/metrics/PlaybackErrorEvent.java +++ b/media/java/android/media/metrics/PlaybackErrorEvent.java @@ -21,6 +21,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -63,11 +64,13 @@ public final class PlaybackErrorEvent extends Event implements Parcelable { @Nullable String exceptionStack, int errorCode, int subErrorCode, - long timeSinceCreatedMillis) { + long timeSinceCreatedMillis, + Bundle extras) { this.mExceptionStack = exceptionStack; this.mErrorCode = errorCode; this.mSubErrorCode = subErrorCode; this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; + this.mExtras = extras.deepCopy(); } /** @hide */ @@ -135,11 +138,13 @@ public final class PlaybackErrorEvent extends Event implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { byte flg = 0; if (mExceptionStack != null) flg |= 0x1; + if (mExtras != null) flg |= 0x2; dest.writeByte(flg); if (mExceptionStack != null) dest.writeString(mExceptionStack); dest.writeInt(mErrorCode); dest.writeInt(mSubErrorCode); dest.writeLong(mTimeSinceCreatedMillis); + if (mExtras != null) dest.writeBundle(mExtras); } @Override @@ -154,11 +159,13 @@ public final class PlaybackErrorEvent extends Event implements Parcelable { int errorCode = in.readInt(); int subErrorCode = in.readInt(); long timeSinceCreatedMillis = in.readLong(); + Bundle extras = (flg & 0x2) == 0 ? null : in.readBundle(); this.mExceptionStack = exceptionStack; this.mErrorCode = errorCode; this.mSubErrorCode = subErrorCode; this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; + this.mExtras = extras; } @@ -183,6 +190,7 @@ public final class PlaybackErrorEvent extends Event implements Parcelable { private int mErrorCode; private int mSubErrorCode; private long mTimeSinceCreatedMillis = -1; + private Bundle mExtras; /** * Creates a new Builder. @@ -226,6 +234,16 @@ public final class PlaybackErrorEvent extends Event implements Parcelable { return this; } + /** + * Set extras for compatibility. + * <p>Should be used by support library only. + * @hide + */ + public @NonNull Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + /** Builds the instance. */ public @NonNull PlaybackErrorEvent build() { @@ -241,7 +259,8 @@ public final class PlaybackErrorEvent extends Event implements Parcelable { stack, mErrorCode, mSubErrorCode, - mTimeSinceCreatedMillis); + mTimeSinceCreatedMillis, + mExtras); return o; } } diff --git a/media/java/android/media/metrics/PlaybackMetrics.java b/media/java/android/media/metrics/PlaybackMetrics.java index 4aa61662ba52..7e7f44a97b9c 100644 --- a/media/java/android/media/metrics/PlaybackMetrics.java +++ b/media/java/android/media/metrics/PlaybackMetrics.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -58,6 +59,10 @@ public final class PlaybackMetrics implements Parcelable { /** SS (HTTP Smooth Streaming) stream type. */ public static final int STREAM_TYPE_SS = 5; + /** Unknown playback type. */ + // TODO: change the PLAYBACK_TYPE_ values + /** @hide */ + public static final int PLAYBACK_TYPE_UNKNOWN = 0; /** VOD (Video on Demand) playback type. */ public static final int PLAYBACK_TYPE_VOD = 0; /** Live playback type. */ @@ -80,6 +85,10 @@ public final class PlaybackMetrics implements Parcelable { /** Clear key DRM type. */ public static final int DRM_TYPE_CLEARKEY = 6; + /** Unknown content type. */ + // TODO: change the CONTENT_TYPE_ values + /** @hide */ + public static final int CONTENT_TYPE_UNKNOWN = 0; /** Main contents. */ public static final int CONTENT_TYPE_MAIN = 0; /** Advertisement contents. */ @@ -112,6 +121,7 @@ public final class PlaybackMetrics implements Parcelable { /** @hide */ @IntDef(prefix = "PLAYBACK_TYPE_", value = { + PLAYBACK_TYPE_UNKNOWN, PLAYBACK_TYPE_VOD, PLAYBACK_TYPE_LIVE, PLAYBACK_TYPE_OTHER @@ -134,6 +144,7 @@ public final class PlaybackMetrics implements Parcelable { /** @hide */ @IntDef(prefix = "CONTENT_TYPE_", value = { + CONTENT_TYPE_UNKNOWN, CONTENT_TYPE_MAIN, CONTENT_TYPE_AD, CONTENT_TYPE_OTHER @@ -158,6 +169,8 @@ public final class PlaybackMetrics implements Parcelable { private final long mNetworkBytesRead; private final long mLocalBytesRead; private final long mNetworkTransferDurationMillis; + private final byte[] mDrmSessionId; + private final Bundle mExtras; /** * Creates a new PlaybackMetrics. @@ -179,7 +192,9 @@ public final class PlaybackMetrics implements Parcelable { int audioUnderrunCount, long networkBytesRead, long localBytesRead, - long networkTransferDurationMillis) { + long networkTransferDurationMillis, + byte[] drmSessionId, + Bundle extras) { this.mMediaDurationMillis = mediaDurationMillis; this.mStreamSource = streamSource; this.mStreamType = streamType; @@ -196,6 +211,8 @@ public final class PlaybackMetrics implements Parcelable { this.mNetworkBytesRead = networkBytesRead; this.mLocalBytesRead = localBytesRead; this.mNetworkTransferDurationMillis = networkTransferDurationMillis; + this.mDrmSessionId = drmSessionId; + this.mExtras = extras.deepCopy(); } /** @@ -321,6 +338,12 @@ public final class PlaybackMetrics implements Parcelable { return mNetworkTransferDurationMillis; } + /** @hide */ + @NonNull + public byte[] getDrmSessionId() { + return mDrmSessionId; + } + @Override public String toString() { return "PlaybackMetrics { " @@ -339,6 +362,7 @@ public final class PlaybackMetrics implements Parcelable { + "networkBytesRead = " + mNetworkBytesRead + ", " + "localBytesRead = " + mLocalBytesRead + ", " + "networkTransferDurationMillis = " + mNetworkTransferDurationMillis + + "drmSessionId = " + Arrays.toString(mDrmSessionId) + " }"; } @@ -361,7 +385,8 @@ public final class PlaybackMetrics implements Parcelable { && mAudioUnderrunCount == that.mAudioUnderrunCount && mNetworkBytesRead == that.mNetworkBytesRead && mLocalBytesRead == that.mLocalBytesRead - && mNetworkTransferDurationMillis == that.mNetworkTransferDurationMillis; + && mNetworkTransferDurationMillis == that.mNetworkTransferDurationMillis + && Arrays.equals(mDrmSessionId, that.mDrmSessionId); } @Override @@ -369,7 +394,7 @@ public final class PlaybackMetrics implements Parcelable { return Objects.hash(mMediaDurationMillis, mStreamSource, mStreamType, mPlaybackType, mDrmType, mContentType, mPlayerName, mPlayerVersion, mExperimentIds, mVideoFramesPlayed, mVideoFramesDropped, mAudioUnderrunCount, mNetworkBytesRead, - mLocalBytesRead, mNetworkTransferDurationMillis); + mLocalBytesRead, mNetworkTransferDurationMillis, mDrmSessionId); } @Override @@ -377,6 +402,7 @@ public final class PlaybackMetrics implements Parcelable { long flg = 0; if (mPlayerName != null) flg |= 0x80; if (mPlayerVersion != null) flg |= 0x100; + if (mExtras != null) flg |= 0x200; dest.writeLong(flg); dest.writeLong(mMediaDurationMillis); dest.writeInt(mStreamSource); @@ -386,6 +412,7 @@ public final class PlaybackMetrics implements Parcelable { dest.writeInt(mContentType); if (mPlayerName != null) dest.writeString(mPlayerName); if (mPlayerVersion != null) dest.writeString(mPlayerVersion); + if (mExtras != null) dest.writeBundle(mExtras); dest.writeLongArray(mExperimentIds); dest.writeInt(mVideoFramesPlayed); dest.writeInt(mVideoFramesDropped); @@ -393,6 +420,8 @@ public final class PlaybackMetrics implements Parcelable { dest.writeLong(mNetworkBytesRead); dest.writeLong(mLocalBytesRead); dest.writeLong(mNetworkTransferDurationMillis); + dest.writeInt(mDrmSessionId.length); + dest.writeByteArray(mDrmSessionId); } @Override @@ -411,6 +440,7 @@ public final class PlaybackMetrics implements Parcelable { int contentType = in.readInt(); String playerName = (flg & 0x80) == 0 ? null : in.readString(); String playerVersion = (flg & 0x100) == 0 ? null : in.readString(); + Bundle extras = (flg & 0x200) == 0 ? null : in.readBundle(); long[] experimentIds = in.createLongArray(); int videoFramesPlayed = in.readInt(); int videoFramesDropped = in.readInt(); @@ -418,6 +448,9 @@ public final class PlaybackMetrics implements Parcelable { long networkBytesRead = in.readLong(); long localBytesRead = in.readLong(); long networkTransferDurationMillis = in.readLong(); + int drmSessionIdLen = in.readInt(); + byte[] drmSessionId = new byte[drmSessionIdLen]; + in.readByteArray(drmSessionId); this.mMediaDurationMillis = mediaDurationMillis; this.mStreamSource = streamSource; @@ -435,6 +468,8 @@ public final class PlaybackMetrics implements Parcelable { this.mNetworkBytesRead = networkBytesRead; this.mLocalBytesRead = localBytesRead; this.mNetworkTransferDurationMillis = networkTransferDurationMillis; + this.mDrmSessionId = drmSessionId; + this.mExtras = extras; } public static final @NonNull Parcelable.Creator<PlaybackMetrics> CREATOR = @@ -470,6 +505,8 @@ public final class PlaybackMetrics implements Parcelable { private long mNetworkBytesRead = -1; private long mLocalBytesRead = -1; private long mNetworkTransferDurationMillis = -1; + private byte[] mDrmSessionId = new byte[0]; + private Bundle mExtras; /** * Creates a new Builder. @@ -608,6 +645,24 @@ public final class PlaybackMetrics implements Parcelable { return this; } + /** + * @hide + */ + public @NonNull Builder setDrmSessionId(@NonNull byte[] drmSessionId) { + mDrmSessionId = drmSessionId; + return this; + } + + /** + * Set extras for compatibility. + * <p>Should be used by support library only. + * @hide + */ + public @NonNull Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull PlaybackMetrics build() { @@ -626,7 +681,9 @@ public final class PlaybackMetrics implements Parcelable { mAudioUnderrunCount, mNetworkBytesRead, mLocalBytesRead, - mNetworkTransferDurationMillis); + mNetworkTransferDurationMillis, + mDrmSessionId, + mExtras); return o; } diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java index 4ee8a45fe196..272fd9bd5576 100644 --- a/media/java/android/media/metrics/PlaybackSession.java +++ b/media/java/android/media/metrics/PlaybackSession.java @@ -29,6 +29,7 @@ import java.util.Objects; public final class PlaybackSession implements AutoCloseable { private final @NonNull String mId; private final @NonNull MediaMetricsManager mManager; + private final @NonNull LogSessionId mLogSessionId; private boolean mClosed = false; /** @@ -41,6 +42,7 @@ public final class PlaybackSession implements AutoCloseable { mManager = manager; AnnotationValidations.validate(NonNull.class, null, mId); AnnotationValidations.validate(NonNull.class, null, mManager); + mLogSessionId = new LogSessionId(mId); } /** @@ -79,9 +81,16 @@ public final class PlaybackSession implements AutoCloseable { } public @NonNull String getId() { + // TODO: remove this method and use getSessionId(); return mId; } + /** @hide */ + public @NonNull LogSessionId getSessionId() { + // TODO: remove getId() and use this method; + return mLogSessionId; + } + @Override public boolean equals(@Nullable Object o) { if (this == o) return true; diff --git a/media/java/android/media/metrics/PlaybackStateEvent.java b/media/java/android/media/metrics/PlaybackStateEvent.java index 8ca5b75dec98..dea8c1db71de 100644 --- a/media/java/android/media/metrics/PlaybackStateEvent.java +++ b/media/java/android/media/metrics/PlaybackStateEvent.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -136,9 +137,11 @@ public final class PlaybackStateEvent extends Event implements Parcelable { */ public PlaybackStateEvent( int state, - long timeSinceCreatedMillis) { + long timeSinceCreatedMillis, + Bundle extras) { this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; this.mState = state; + this.mExtras = extras.deepCopy(); } /** @@ -174,8 +177,12 @@ public final class PlaybackStateEvent extends Event implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + byte flg = 0; + if (mExtras != null) flg |= 0x1; + dest.writeByte(flg); dest.writeInt(mState); dest.writeLong(mTimeSinceCreatedMillis); + if (mExtras != null) dest.writeBundle(mExtras); } @Override @@ -185,11 +192,14 @@ public final class PlaybackStateEvent extends Event implements Parcelable { /** @hide */ /* package-private */ PlaybackStateEvent(@NonNull Parcel in) { + byte flg = in.readByte(); int state = in.readInt(); long timeSinceCreatedMillis = in.readLong(); + Bundle extras = (flg & 0x1) == 0 ? null : in.readBundle(); this.mState = state; this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; + this.mExtras = extras; } public static final @NonNull Parcelable.Creator<PlaybackStateEvent> CREATOR = @@ -211,6 +221,7 @@ public final class PlaybackStateEvent extends Event implements Parcelable { public static final class Builder { private int mState = STATE_NOT_STARTED; private long mTimeSinceCreatedMillis = -1; + private Bundle mExtras; /** * Creates a new Builder. @@ -236,11 +247,22 @@ public final class PlaybackStateEvent extends Event implements Parcelable { return this; } + /** + * Set extras for compatibility. + * <p>Should be used by support library only. + * @hide + */ + public @NonNull Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + /** Builds the instance. */ public @NonNull PlaybackStateEvent build() { PlaybackStateEvent o = new PlaybackStateEvent( mState, - mTimeSinceCreatedMillis); + mTimeSinceCreatedMillis, + mExtras); return o; } } diff --git a/media/java/android/media/metrics/RecordingSession.java b/media/java/android/media/metrics/RecordingSession.java new file mode 100644 index 000000000000..541d129604ad --- /dev/null +++ b/media/java/android/media/metrics/RecordingSession.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.metrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.Objects; + +/** + * An instances of this class represents a session of media recording. + * @hide + */ +public final class RecordingSession implements AutoCloseable { + private final @NonNull String mId; + private final @NonNull MediaMetricsManager mManager; + private final @NonNull LogSessionId mLogSessionId; + private boolean mClosed = false; + + /** @hide */ + public RecordingSession(@NonNull String id, @NonNull MediaMetricsManager manager) { + mId = id; + mManager = manager; + AnnotationValidations.validate(NonNull.class, null, mId); + AnnotationValidations.validate(NonNull.class, null, mManager); + mLogSessionId = new LogSessionId(mId); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RecordingSession that = (RecordingSession) o; + return Objects.equals(mId, that.mId); + } + + @Override + public int hashCode() { + return Objects.hash(mId); + } + + @Override + public void close() { + mClosed = true; + } +} diff --git a/media/java/android/media/metrics/TrackChangeEvent.java b/media/java/android/media/metrics/TrackChangeEvent.java index ef25357457c5..aa519782ec80 100644 --- a/media/java/android/media/metrics/TrackChangeEvent.java +++ b/media/java/android/media/metrics/TrackChangeEvent.java @@ -16,10 +16,12 @@ package android.media.metrics; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -65,9 +67,10 @@ public final class TrackChangeEvent extends Event implements Parcelable { private final @Nullable String mLanguage; private final @Nullable String mLanguageRegion; private final int mChannelCount; - private final int mSampleRate; + private final int mAudioSampleRate; private final int mWidth; private final int mHeight; + private final float mVideoFrameRate; @@ -99,6 +102,7 @@ public final class TrackChangeEvent extends Event implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface TrackType {} + // TODO: remove this constructor. Use the private one below. public TrackChangeEvent( int state, int reason, @@ -125,9 +129,45 @@ public final class TrackChangeEvent extends Event implements Parcelable { this.mLanguage = language; this.mLanguageRegion = languageRegion; this.mChannelCount = channelCount; - this.mSampleRate = sampleRate; + this.mAudioSampleRate = sampleRate; this.mWidth = width; this.mHeight = height; + this.mVideoFrameRate = -1; + } + + private TrackChangeEvent( + int state, + int reason, + @Nullable String containerMimeType, + @Nullable String sampleMimeType, + @Nullable String codecName, + int bitrate, + long timeSinceCreatedMillis, + int type, + @Nullable String language, + @Nullable String languageRegion, + int channelCount, + int sampleRate, + int width, + int height, + float videoFrameRate, + @Nullable Bundle extras) { + this.mState = state; + this.mReason = reason; + this.mContainerMimeType = containerMimeType; + this.mSampleMimeType = sampleMimeType; + this.mCodecName = codecName; + this.mBitrate = bitrate; + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; + this.mType = type; + this.mLanguage = language; + this.mLanguageRegion = languageRegion; + this.mChannelCount = channelCount; + this.mAudioSampleRate = sampleRate; + this.mWidth = width; + this.mHeight = height; + this.mVideoFrameRate = videoFrameRate; + this.mExtras = extras.deepCopy(); } /** @@ -223,7 +263,7 @@ public final class TrackChangeEvent extends Event implements Parcelable { */ @IntRange(from = -1, to = Integer.MAX_VALUE) public int getSampleRate() { - return mSampleRate; + return mAudioSampleRate; } /** @@ -244,6 +284,16 @@ public final class TrackChangeEvent extends Event implements Parcelable { return mHeight; } + /** + * Gets video frame rate. + * @return the video frame rate, or -1 if unknown. + * @hide + */ + @FloatRange(from = -1, to = Float.MAX_VALUE) + public float getVideoFrameRate() { + return mVideoFrameRate; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { int flg = 0; @@ -252,6 +302,7 @@ public final class TrackChangeEvent extends Event implements Parcelable { if (mCodecName != null) flg |= 0x10; if (mLanguage != null) flg |= 0x100; if (mLanguageRegion != null) flg |= 0x200; + if (mExtras != null) flg |= 0x400; dest.writeInt(flg); dest.writeInt(mState); dest.writeInt(mReason); @@ -264,9 +315,11 @@ public final class TrackChangeEvent extends Event implements Parcelable { if (mLanguage != null) dest.writeString(mLanguage); if (mLanguageRegion != null) dest.writeString(mLanguageRegion); dest.writeInt(mChannelCount); - dest.writeInt(mSampleRate); + dest.writeInt(mAudioSampleRate); dest.writeInt(mWidth); dest.writeInt(mHeight); + dest.writeFloat(mVideoFrameRate); + if (mExtras != null) dest.writeBundle(mExtras); } @Override @@ -291,6 +344,8 @@ public final class TrackChangeEvent extends Event implements Parcelable { int sampleRate = in.readInt(); int width = in.readInt(); int height = in.readInt(); + float videoFrameRate = in.readFloat(); + Bundle extras = (flg & 0x400) == 0 ? null : in.readBundle(); this.mState = state; this.mReason = reason; @@ -303,9 +358,11 @@ public final class TrackChangeEvent extends Event implements Parcelable { this.mLanguage = language; this.mLanguageRegion = languageRegion; this.mChannelCount = channelCount; - this.mSampleRate = sampleRate; + this.mAudioSampleRate = sampleRate; this.mWidth = width; this.mHeight = height; + this.mVideoFrameRate = videoFrameRate; + this.mExtras = extras; } public static final @NonNull Parcelable.Creator<TrackChangeEvent> CREATOR = @@ -335,9 +392,10 @@ public final class TrackChangeEvent extends Event implements Parcelable { + "language = " + mLanguage + ", " + "languageRegion = " + mLanguageRegion + ", " + "channelCount = " + mChannelCount + ", " - + "sampleRate = " + mSampleRate + ", " + + "sampleRate = " + mAudioSampleRate + ", " + "width = " + mWidth + ", " - + "height = " + mHeight + + "height = " + mHeight + ", " + + "videoFrameRate = " + mVideoFrameRate + " }"; } @@ -357,16 +415,17 @@ public final class TrackChangeEvent extends Event implements Parcelable { && Objects.equals(mLanguage, that.mLanguage) && Objects.equals(mLanguageRegion, that.mLanguageRegion) && mChannelCount == that.mChannelCount - && mSampleRate == that.mSampleRate + && mAudioSampleRate == that.mAudioSampleRate && mWidth == that.mWidth - && mHeight == that.mHeight; + && mHeight == that.mHeight + && mVideoFrameRate == that.mVideoFrameRate; } @Override public int hashCode() { return Objects.hash(mState, mReason, mContainerMimeType, mSampleMimeType, mCodecName, mBitrate, mTimeSinceCreatedMillis, mType, mLanguage, mLanguageRegion, - mChannelCount, mSampleRate, mWidth, mHeight); + mChannelCount, mAudioSampleRate, mWidth, mHeight, mVideoFrameRate); } /** @@ -385,9 +444,11 @@ public final class TrackChangeEvent extends Event implements Parcelable { private @Nullable String mLanguage; private @Nullable String mLanguageRegion; private int mChannelCount = -1; - private int mSampleRate = -1; + private int mAudioSampleRate = -1; private int mWidth = -1; private int mHeight = -1; + private float mVideoFrameRate = -1; + private Bundle mExtras; private long mBuilderFieldsSet = 0L; @@ -512,9 +573,10 @@ public final class TrackChangeEvent extends Event implements Parcelable { */ public @NonNull Builder setSampleRate( @IntRange(from = -1, to = Integer.MAX_VALUE) int value) { + // TODO: rename it to setAudioSampleRate checkNotUsed(); mBuilderFieldsSet |= 0x800; - mSampleRate = value; + mAudioSampleRate = value; return this; } @@ -540,6 +602,28 @@ public final class TrackChangeEvent extends Event implements Parcelable { return this; } + /** + * Sets video frame rate. + * @param value the video frame rate. -1 indicates the value is unknown. + * @hide + */ + public @NonNull Builder setVideoFrameRate( + @FloatRange(from = -1, to = Float.MAX_VALUE) float value) { + checkNotUsed(); + mVideoFrameRate = value; + return this; + } + + /** + * Set extras for compatibility. + * <p>Should be used by support library only. + * @hide + */ + public @NonNull Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull TrackChangeEvent build() { checkNotUsed(); @@ -557,9 +641,11 @@ public final class TrackChangeEvent extends Event implements Parcelable { mLanguage, mLanguageRegion, mChannelCount, - mSampleRate, + mAudioSampleRate, mWidth, - mHeight); + mHeight, + mVideoFrameRate, + mExtras); return o; } diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp index 307d80dc15c0..07866ac34e4c 100644 --- a/media/jni/android_media_MediaCodecList.cpp +++ b/media/jni/android_media_MediaCodecList.cpp @@ -105,6 +105,7 @@ static const JavaMediaCodecListWrapper *getCodecList(JNIEnv *env) { // This should never happen unless something is really wrong jniThrowException( env, "java/lang/RuntimeException", "cannot get MediaCodecList"); + return NULL; } sListWrapper.reset(new JavaMediaCodecListWrapper(mcl)); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 56f6c45bb50e..53f6fe24556b 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -445,8 +445,7 @@ static bool throwExceptionAsNecessary( jniThrowException(env, "android/media/DeniedByServerException", msg); return true; } else if (err == DEAD_OBJECT) { - jniThrowException(env, "android/media/MediaDrmResetException", - "mediaserver died"); + jniThrowException(env, "android/media/MediaDrmResetException", msg); return true; } else if (isSessionException(err)) { throwSessionException(env, msg, err); @@ -967,10 +966,12 @@ static void android_media_MediaDrm_native_setup( status_t err = drm->initCheck(); if (err != OK) { + auto logs(DrmUtils::gLogBuf.getLogs()); + auto msg(DrmUtils::GetExceptionMessage(err, "Failed to instantiate drm object", logs)); jniThrowException( env, "android/media/UnsupportedSchemeException", - "Failed to instantiate drm object."); + msg.c_str()); return; } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index e620dfbafa08..8b448877c15b 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -107,12 +107,10 @@ public class CompanionDeviceDiscoveryService extends Service { String callingPackage, IFindDeviceCallback findCallback, AndroidFuture serviceCallback) { - if (DEBUG) { - Log.i(LOG_TAG, - "startDiscovery() called with: filter = [" + request - + "], findCallback = [" + findCallback + "]" - + "], serviceCallback = [" + serviceCallback + "]"); - } + Log.i(LOG_TAG, + "startDiscovery() called with: filter = [" + request + + "], findCallback = [" + findCallback + "]" + + "], serviceCallback = [" + serviceCallback + "]"); mFindCallback = findCallback; mServiceCallback = serviceCallback; Handler.getMain().sendMessage(obtainMessage( @@ -127,7 +125,7 @@ public class CompanionDeviceDiscoveryService extends Service { @Override public IBinder onBind(Intent intent) { - if (DEBUG) Log.i(LOG_TAG, "onBind(" + intent + ")"); + Log.i(LOG_TAG, "onBind(" + intent + ")"); return mBinder.asBinder(); } @@ -135,7 +133,7 @@ public class CompanionDeviceDiscoveryService extends Service { public void onCreate() { super.onCreate(); - if (DEBUG) Log.i(LOG_TAG, "onCreate()"); + Log.i(LOG_TAG, "onCreate()"); mBluetoothManager = getSystemService(BluetoothManager.class); mBluetoothAdapter = mBluetoothManager.getAdapter(); @@ -160,7 +158,9 @@ public class CompanionDeviceDiscoveryService extends Service { = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter); reset(); - } else if (DEBUG) Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request); + } else { + Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request); + } if (!ArrayUtils.isEmpty(mDevicesFound)) { onReadyToShowUI(); @@ -197,17 +197,20 @@ public class CompanionDeviceDiscoveryService extends Service { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothDevice.ACTION_FOUND); + Log.i(LOG_TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)"); mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver(); registerReceiver(mBluetoothBroadcastReceiver, intentFilter); mBluetoothAdapter.startDiscovery(); } if (shouldScan(mBLEFilters) && mBLEScanner != null) { + Log.i(LOG_TAG, "BLEScanner.startScan"); mBLEScanCallback = new BLEScanCallback(); mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback); } if (shouldScan(mWifiFilters)) { + Log.i(LOG_TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)"); mWifiBroadcastReceiver = new WifiBroadcastReceiver(); registerReceiver(mWifiBroadcastReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); @@ -225,7 +228,7 @@ public class CompanionDeviceDiscoveryService extends Service { @MainThread private void reset() { - if (DEBUG) Log.i(LOG_TAG, "reset()"); + Log.i(LOG_TAG, "reset()"); stopScan(); mDevicesFound.clear(); mSelectedDevice = null; @@ -234,12 +237,13 @@ public class CompanionDeviceDiscoveryService extends Service { @Override public boolean onUnbind(Intent intent) { + Log.i(LOG_TAG, "onUnbind(intent = " + intent + ")"); stopScan(); return super.onUnbind(intent); } private void stopScan() { - if (DEBUG) Log.i(LOG_TAG, "stopScan()"); + Log.i(LOG_TAG, "stopScan()"); if (!mIsScanning) return; mIsScanning = false; diff --git a/core/java/android/net/QosFilterParcelable.aidl b/packages/Connectivity/framework/aidl-export/android/net/QosFilterParcelable.aidl index 312d6352ee92..312d6352ee92 100644 --- a/core/java/android/net/QosFilterParcelable.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/QosFilterParcelable.aidl diff --git a/core/java/android/net/QosSession.aidl b/packages/Connectivity/framework/aidl-export/android/net/QosSession.aidl index c2cf36624b55..c2cf36624b55 100644 --- a/core/java/android/net/QosSession.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/QosSession.aidl diff --git a/core/java/android/net/QosSocketInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/QosSocketInfo.aidl index 476c0900e23e..476c0900e23e 100644 --- a/core/java/android/net/QosSocketInfo.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/QosSocketInfo.aidl diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt index 31b8fc8ae53a..a8f1a4d2a7f8 100644 --- a/packages/Connectivity/framework/api/current.txt +++ b/packages/Connectivity/framework/api/current.txt @@ -401,16 +401,6 @@ package android.net { method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); } - public final class Proxy { - ctor public Proxy(); - method @Deprecated public static String getDefaultHost(); - method @Deprecated public static int getDefaultPort(); - method @Deprecated public static String getHost(android.content.Context); - method @Deprecated public static int getPort(android.content.Context); - field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; - field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; - } - public class ProxyInfo implements android.os.Parcelable { ctor public ProxyInfo(@Nullable android.net.ProxyInfo); method public static android.net.ProxyInfo buildDirectProxy(String, int); diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index 3af855ec1e11..d2ed73ef8298 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -6,6 +6,7 @@ package android.net { } public class ConnectivityManager { + 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 @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, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); 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); @@ -23,10 +24,6 @@ package android.net { field public static final int TRANSPORT_TEST = 7; // 0x7 } - public final class Proxy { - method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); - } - 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 41ebc5774f3d..f5972fa34042 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -2,7 +2,7 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { - method public void logEvent(int, @NonNull String); + method @Deprecated public void logEvent(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); method public void useNetwork(); field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 @@ -308,6 +308,9 @@ package android.net { field public static final int ID_NONE = -1; // 0xffffffff } + public class NetworkReleasedException extends java.lang.Exception { + } + public class NetworkRequest implements android.os.Parcelable { method @Nullable public String getRequestorPackageName(); method public int getRequestorUid(); @@ -317,6 +320,47 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); } + public abstract class QosCallback { + ctor public QosCallback(); + method public void onError(@NonNull android.net.QosCallbackException); + method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes); + method public void onQosSessionLost(@NonNull android.net.QosSession); + } + + public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException { + } + + public final class QosCallbackException extends java.lang.Exception { + } + + public abstract class QosFilter { + method @NonNull public abstract android.net.Network getNetwork(); + method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int); + } + + public final class QosSession implements android.os.Parcelable { + ctor public QosSession(int, int); + method public int describeContents(); + method public int getSessionId(); + method public int getSessionType(); + method public long getUniqueId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR; + field public static final int TYPE_EPS_BEARER = 1; // 0x1 + } + + public interface QosSessionAttributes { + } + + public final class QosSocketInfo implements android.os.Parcelable { + ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException; + method public int describeContents(); + method @NonNull public java.net.InetSocketAddress getLocalSocketAddress(); + method @NonNull public android.net.Network getNetwork(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR; + } + public final class RouteInfo implements android.os.Parcelable { ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); @@ -331,6 +375,12 @@ package android.net { field public static final int SUCCESS = 0; // 0x0 } + public class SocketLocalAddressChangedException extends java.lang.Exception { + } + + public class SocketNotBoundException extends java.lang.Exception { + } + public final class StaticIpConfiguration implements android.os.Parcelable { ctor public StaticIpConfiguration(); ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); @@ -392,16 +442,3 @@ package android.net.apf { } -package android.net.util { - - public final class SocketUtils { - method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; - method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; - method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); - method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); - } - -} - diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortal.java b/packages/Connectivity/framework/src/android/net/CaptivePortal.java index 269bbf20c8b1..4a7b6016427b 100644 --- a/packages/Connectivity/framework/src/android/net/CaptivePortal.java +++ b/packages/Connectivity/framework/src/android/net/CaptivePortal.java @@ -160,12 +160,11 @@ public class CaptivePortal implements Parcelable { * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. * @param packageName captive portal application package name. * @hide + * @deprecated The event will not be logged in Android S and above. The + * caller is migrating to statsd. */ + @Deprecated @SystemApi public void logEvent(int eventId, @NonNull String packageName) { - try { - ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName); - } catch (RemoteException e) { - } } } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index e7ab0a1c7ac8..d7c6854496b7 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -1259,6 +1259,25 @@ public class ConnectivityManager { } /** + * Return a list of {@link NetworkStateSnapshot}s, one for each network that is currently + * connected. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @NonNull + public List<NetworkStateSnapshot> getAllNetworkStateSnapshot() { + try { + return mService.getAllNetworkStateSnapshot(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the {@link Network} object currently serving a given type, or * null if the given type is not connected. * @@ -2245,31 +2264,6 @@ public class ConnectivityManager { } } - /* TODO: These permissions checks don't belong in client-side code. Move them to - * services.jar, possibly in com.android.server.net. */ - - /** {@hide} */ - public static final void enforceChangePermission(Context context, - String callingPkg, String callingAttributionTag) { - int uid = Binder.getCallingUid(); - checkAndNoteChangeNetworkStateOperation(context, uid, callingPkg, - callingAttributionTag, true /* throwException */); - } - - /** - * Check if the package is a allowed to change the network state. This also accounts that such - * an access happened. - * - * @return {@code true} iff the package is allowed to change the network state. - */ - // TODO: Remove method and replace with direct call once R code is pushed to AOSP - private static boolean checkAndNoteChangeNetworkStateOperation(@NonNull Context context, - int uid, @NonNull String callingPackage, @Nullable String callingAttributionTag, - boolean throwException) { - return Settings.checkAndNoteChangeNetworkStateOperation(context, uid, callingPackage, - callingAttributionTag, throwException); - } - /** * Check if the package is a allowed to write settings. This also accounts that such an access * happened. diff --git a/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl b/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl index fe21905c7002..e35f8d46afe7 100644 --- a/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl +++ b/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl @@ -23,5 +23,4 @@ package android.net; oneway interface ICaptivePortal { void appRequest(int request); void appResponse(int response); - void logEvent(int eventId, String packageName); } diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 160338d396af..cd49258d1c47 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -31,6 +31,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.OemNetworkPreferences; import android.net.ProxyInfo; import android.net.UidRange; @@ -79,6 +80,8 @@ interface IConnectivityManager @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) NetworkState[] getAllNetworkState(); + List<NetworkStateSnapshot> getAllNetworkStateSnapshot(); + boolean isActiveNetworkMetered(); boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, diff --git a/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl b/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl index 7979afc54f90..7979afc54f90 100644 --- a/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl +++ b/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl diff --git a/core/java/android/net/IQosCallback.aidl b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl index 91c75759f85c..91c75759f85c 100644 --- a/core/java/android/net/IQosCallback.aidl +++ b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl diff --git a/core/java/android/net/NetworkReleasedException.java b/packages/Connectivity/framework/src/android/net/NetworkReleasedException.java index 0629b7563aea..0629b7563aea 100644 --- a/core/java/android/net/NetworkReleasedException.java +++ b/packages/Connectivity/framework/src/android/net/NetworkReleasedException.java diff --git a/core/java/android/net/NetworkState.java b/packages/Connectivity/framework/src/android/net/NetworkState.java index 813fde1c15f2..d01026566ca0 100644 --- a/core/java/android/net/NetworkState.java +++ b/packages/Connectivity/framework/src/android/net/NetworkState.java @@ -115,7 +115,8 @@ public class NetworkState implements Parcelable { } @UnsupportedAppUsage - public static final @android.annotation.NonNull Creator<NetworkState> CREATOR = new Creator<NetworkState>() { + @NonNull + public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() { @Override public NetworkState createFromParcel(Parcel in) { return new NetworkState(in); diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java index b5e8a614b8ea..9e42bbecbe9d 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java +++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java @@ -87,22 +87,6 @@ public class NetworkUtils { public static native int bindSocketToNetwork(FileDescriptor fd, int netId); /** - * Protect {@code fd} from VPN connections. After protecting, data sent through - * this socket will go directly to the underlying network, so its traffic will not be - * forwarded through the VPN. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553, - publicAlternatives = "Use {@link android.net.VpnService#protect} instead.") - public static native boolean protectFromVpn(FileDescriptor fd); - - /** - * Protect {@code socketfd} from VPN connections. After protecting, data sent through - * this socket will go directly to the underlying network, so its traffic will not be - * forwarded through the VPN. - */ - public native static boolean protectFromVpn(int socketfd); - - /** * Determine if {@code uid} can access network designated by {@code netId}. * @return {@code true} if {@code uid} can access network, {@code false} otherwise. */ diff --git a/core/java/android/net/QosCallback.java b/packages/Connectivity/framework/src/android/net/QosCallback.java index 22f06bc0e690..22f06bc0e690 100644 --- a/core/java/android/net/QosCallback.java +++ b/packages/Connectivity/framework/src/android/net/QosCallback.java diff --git a/core/java/android/net/QosCallbackConnection.java b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java index bdb4ad68cd7b..bdb4ad68cd7b 100644 --- a/core/java/android/net/QosCallbackConnection.java +++ b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java diff --git a/core/java/android/net/QosCallbackException.java b/packages/Connectivity/framework/src/android/net/QosCallbackException.java index 7fd9a527e2ac..7fd9a527e2ac 100644 --- a/core/java/android/net/QosCallbackException.java +++ b/packages/Connectivity/framework/src/android/net/QosCallbackException.java diff --git a/core/java/android/net/QosFilter.java b/packages/Connectivity/framework/src/android/net/QosFilter.java index ab55002e02b3..ab55002e02b3 100644 --- a/core/java/android/net/QosFilter.java +++ b/packages/Connectivity/framework/src/android/net/QosFilter.java diff --git a/core/java/android/net/QosFilterParcelable.java b/packages/Connectivity/framework/src/android/net/QosFilterParcelable.java index da3b2cf8ff7a..da3b2cf8ff7a 100644 --- a/core/java/android/net/QosFilterParcelable.java +++ b/packages/Connectivity/framework/src/android/net/QosFilterParcelable.java diff --git a/core/java/android/net/QosSession.java b/packages/Connectivity/framework/src/android/net/QosSession.java index 4f3bb77c5877..4f3bb77c5877 100644 --- a/core/java/android/net/QosSession.java +++ b/packages/Connectivity/framework/src/android/net/QosSession.java diff --git a/core/java/android/net/QosSessionAttributes.java b/packages/Connectivity/framework/src/android/net/QosSessionAttributes.java index 7a885942d1b5..7a885942d1b5 100644 --- a/core/java/android/net/QosSessionAttributes.java +++ b/packages/Connectivity/framework/src/android/net/QosSessionAttributes.java diff --git a/core/java/android/net/QosSocketFilter.java b/packages/Connectivity/framework/src/android/net/QosSocketFilter.java index 2080e68f5fba..2080e68f5fba 100644 --- a/core/java/android/net/QosSocketFilter.java +++ b/packages/Connectivity/framework/src/android/net/QosSocketFilter.java diff --git a/core/java/android/net/QosSocketInfo.java b/packages/Connectivity/framework/src/android/net/QosSocketInfo.java index d37c4691ddde..d37c4691ddde 100644 --- a/core/java/android/net/QosSocketInfo.java +++ b/packages/Connectivity/framework/src/android/net/QosSocketInfo.java diff --git a/core/java/android/net/SocketLocalAddressChangedException.java b/packages/Connectivity/framework/src/android/net/SocketLocalAddressChangedException.java index 9daad83fd13e..9daad83fd13e 100644 --- a/core/java/android/net/SocketLocalAddressChangedException.java +++ b/packages/Connectivity/framework/src/android/net/SocketLocalAddressChangedException.java diff --git a/core/java/android/net/SocketNotBoundException.java b/packages/Connectivity/framework/src/android/net/SocketNotBoundException.java index b1d7026ac981..b1d7026ac981 100644 --- a/core/java/android/net/SocketNotBoundException.java +++ b/packages/Connectivity/framework/src/android/net/SocketNotBoundException.java diff --git a/core/java/android/net/UidRange.aidl b/packages/Connectivity/framework/src/android/net/UidRange.aidl index f70fc8e2fefd..f70fc8e2fefd 100644 --- a/core/java/android/net/UidRange.aidl +++ b/packages/Connectivity/framework/src/android/net/UidRange.aidl diff --git a/core/java/android/net/UidRange.java b/packages/Connectivity/framework/src/android/net/UidRange.java index f0e7da78d669..26518d32edcb 100644 --- a/core/java/android/net/UidRange.java +++ b/packages/Connectivity/framework/src/android/net/UidRange.java @@ -16,8 +16,6 @@ package android.net; -import static android.os.UserHandle.PER_USER_RANGE; - import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -52,14 +50,15 @@ public final class UidRange implements Parcelable { /** Returns the smallest user Id which is contained in this UidRange */ public int getStartUser() { - return start / PER_USER_RANGE; + return UserHandle.getUserHandleForUid(start).getIdentifier(); } /** Returns the largest user Id which is contained in this UidRange */ public int getEndUser() { - return stop / PER_USER_RANGE; + return UserHandle.getUserHandleForUid(stop).getIdentifier(); } + /** Returns whether the UidRange contains the specified UID. */ public boolean contains(int uid) { return start <= uid && uid <= stop; } @@ -72,7 +71,7 @@ public final class UidRange implements Parcelable { } /** - * @return {@code true} if this range contains every UID contained by the {@param other} range. + * @return {@code true} if this range contains every UID contained by the {@code other} range. */ public boolean containsRange(UidRange other) { return start <= other.start && other.stop <= stop; @@ -118,18 +117,18 @@ public final class UidRange implements Parcelable { } public static final @android.annotation.NonNull Creator<UidRange> CREATOR = - new Creator<UidRange>() { - @Override - public UidRange createFromParcel(Parcel in) { - int start = in.readInt(); - int stop = in.readInt(); + new Creator<UidRange>() { + @Override + public UidRange createFromParcel(Parcel in) { + int start = in.readInt(); + int stop = in.readInt(); - return new UidRange(start, stop); - } - @Override - public UidRange[] newArray(int size) { - return new UidRange[size]; - } + return new UidRange(start, stop); + } + @Override + public UidRange[] newArray(int size) { + return new UidRange[size]; + } }; /** diff --git a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java index 43fffd733e91..739ddada50b4 100644 --- a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -30,8 +30,8 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; -import android.telephony.PhoneStateListener; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.Log; @@ -92,8 +92,8 @@ public class MultinetworkPolicyTracker { } @VisibleForTesting - protected class ActiveDataSubscriptionIdChangedListener extends PhoneStateListener - implements PhoneStateListener.ActiveDataSubscriptionIdChangedListener { + protected class ActiveDataSubscriptionIdListener extends TelephonyCallback + implements TelephonyCallback.ActiveDataSubscriptionIdListener { @Override public void onActiveDataSubscriptionIdChanged(int subId) { mActiveSubId = subId; @@ -121,8 +121,8 @@ public class MultinetworkPolicyTracker { } }; - ctx.getSystemService(TelephonyManager.class).registerPhoneStateListener( - new HandlerExecutor(handler), new ActiveDataSubscriptionIdChangedListener()); + ctx.getSystemService(TelephonyManager.class).registerTelephonyCallback( + new HandlerExecutor(handler), new ActiveDataSubscriptionIdListener()); updateAvoidBadWifi(); updateMeteredMultipathPreference(); diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index f20b89fb842c..e65b7b423bdc 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -63,6 +63,7 @@ java_library { "unsupportedappusage", ], static_libs: [ + "modules-utils-os", "net-utils-device-common", "net-utils-framework-common", "netd-client", diff --git a/packages/Connectivity/service/jarjar-rules.txt b/packages/Connectivity/service/jarjar-rules.txt index ef53ebb43c40..d8205bf780fd 100644 --- a/packages/Connectivity/service/jarjar-rules.txt +++ b/packages/Connectivity/service/jarjar-rules.txt @@ -1 +1,2 @@ -rule com.android.net.module.util.** com.android.connectivity.util.@1
\ No newline at end of file +rule com.android.net.module.util.** com.android.connectivity.net-utils.@1 +rule com.android.modules.utils.** com.android.connectivity.modules-utils.@1
\ No newline at end of file diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 7f19662c6961..c1dca5df1b2f 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -394,16 +394,20 @@ public class DynamicSystemInstallationService extends Service } private void executeNotifyIfInUseCommand() { - int status = getStatus(); - - if (status == STATUS_IN_USE) { - startForeground(NOTIFICATION_ID, - buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); - } else if (status == STATUS_READY) { - startForeground(NOTIFICATION_ID, - buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); - } else { - stopSelf(); + switch (getStatus()) { + case STATUS_IN_USE: + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); + break; + case STATUS_READY: + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); + break; + case STATUS_IN_PROGRESS: + break; + case STATUS_NOT_STARTED: + default: + stopSelf(); } } diff --git a/packages/LocalTransport/OWNERS b/packages/LocalTransport/OWNERS new file mode 100644 index 000000000000..d99779e3d9da --- /dev/null +++ b/packages/LocalTransport/OWNERS @@ -0,0 +1 @@ +include /services/backup/OWNERS diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java index 139c8e59a148..63edc776b3cb 100644 --- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java @@ -165,6 +165,9 @@ public class LocalTransport extends BackupTransport { if (mParameters.isDeviceTransfer()) { flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER; } + if (mParameters.isEncrypted()) { + flags |= BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; + } return flags; } diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java index 2946db3cdcf0..1ba1bc6bfec7 100644 --- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java @@ -28,10 +28,12 @@ public class LocalTransportParameters extends KeyValueSettingObserver { private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag"; private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only"; private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer"; + private static final String KEY_IS_ENCRYPTED = "is_encrypted"; private boolean mFakeEncryptionFlag; private boolean mIsNonIncrementalOnly; private boolean mIsDeviceTransfer; + private boolean mIsEncrypted; public LocalTransportParameters(Handler handler, ContentResolver resolver) { super(handler, resolver, Settings.Secure.getUriFor(SETTING)); @@ -49,6 +51,10 @@ public class LocalTransportParameters extends KeyValueSettingObserver { return mIsDeviceTransfer; } + boolean isEncrypted() { + return mIsEncrypted; + } + public String getSettingValue(ContentResolver resolver) { return Settings.Secure.getString(resolver, SETTING); } @@ -57,5 +63,6 @@ public class LocalTransportParameters extends KeyValueSettingObserver { mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false); mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false); mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false); + mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false); } } diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml new file mode 100644 index 000000000000..c799b9962828 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<!-- The main content view --> +<LinearLayout + android:id="@+id/content_parent" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + android:transitionGroup="true" + android:orientation="vertical"> + <Toolbar + android:id="@+id/action_bar" + style="?android:attr/actionBarStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="?android:attr/actionBarTheme" /> + <FrameLayout + android:id="@+id/content_frame" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java index 637805fc81c3..ad94cd0318a7 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java @@ -24,6 +24,7 @@ import android.view.ViewGroup; import android.widget.Toolbar; import androidx.annotation.Nullable; +import androidx.core.os.BuildCompat; import androidx.fragment.app.FragmentActivity; import com.google.android.material.appbar.CollapsingToolbarLayout; @@ -40,8 +41,15 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - super.setContentView(R.layout.collapsing_toolbar_base_layout); - mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); + // TODO(b/181723278): Update the version check after SDK for S is finalized + // The collapsing toolbar is only supported if the android platform version is S or higher. + // Otherwise the regular action bar will be shown. + if (BuildCompat.isAtLeastS()) { + super.setContentView(R.layout.collapsing_toolbar_base_layout); + mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); + } else { + super.setContentView(R.layout.toolbar_base_layout); + } final Toolbar toolbar = findViewById(R.id.action_bar); setActionBar(toolbar); @@ -90,6 +98,14 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { super.setTitle(titleId); } + @Override + public boolean onNavigateUp() { + if (!super.onNavigateUp()) { + finish(); + } + return true; + } + /** * Returns an instance of collapsing toolbar. */ diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 79fbcc376b3c..efa9f3c16b48 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -64,8 +64,12 @@ <string name="wifi_security_eap" translatable="false">WPA/WPA2/WPA3-Enterprise</string> <!-- Do not translate. Concise terminology for wifi with WPA 802.1x EAP security --> <string name="wifi_security_eap_wpa" translatable="false">WPA-Enterprise</string> + <!-- Do not translate. Concise terminology for wifi with 802.1x EAP security --> + <string name="wifi_security_eap_wpa_wpa2" translatable="false">WPA/WPA2-Enterprise</string> <!-- Do not translate. Concise terminology for wifi with WPA2/WPA3 802.1x EAP security --> <string name="wifi_security_eap_wpa2_wpa3" translatable="false">WPA2/WPA3-Enterprise</string> + <!-- Do not translate. Concise terminology for wifi with WPA3 802.1x EAP security --> + <string name="wifi_security_eap_wpa3" translatable="false">WPA3-Enterprise</string> <!-- Do not translate. Concise terminology for Passpoint network --> <string name="wifi_security_passpoint" translatable="false">Passpoint</string> <!-- Do not translate. Terminology for wifi with WPA3 security --> @@ -1059,7 +1063,14 @@ <!-- Title for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> <string name="accessibility_display_daltonizer_preference_title">Color correction</string> <!-- Subtitle for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> - <string name="accessibility_display_daltonizer_preference_subtitle"><![CDATA[Color correction allows you to adjust how colors are displayed on your device]]></string> + <string name="accessibility_display_daltonizer_preference_subtitle"> + <![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> + </ol> + ]]></string> <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] --> <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string> @@ -1295,6 +1306,8 @@ <!-- Name of the phone device. [CHAR LIMIT=30] --> <string name="media_transfer_this_device_name">Phone speaker</string> + <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] --> + <string name="media_transfer_this_phone">This phone</string> <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] --> <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java index 43717aba3abd..dfde3c7a2512 100644 --- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java @@ -27,7 +27,7 @@ import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback; import android.os.Handler; import android.os.HandlerExecutor; import android.provider.Settings; -import android.telephony.PhoneStateListener; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.Log; @@ -72,7 +72,10 @@ public class ConnectivitySubsystemsRecoveryManager { checkIfAllSubsystemsRestartsAreDone(); } }; - private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + private final MobileTelephonyCallback mTelephonyCallback = new MobileTelephonyCallback(); + + private class MobileTelephonyCallback extends TelephonyCallback implements + TelephonyCallback.RadioPowerStateListener { @Override public void onRadioPowerStateChanged(int state) { if (!mTelephonyRestartInProgress || mCurrentRecoveryCallback == null) { @@ -85,7 +88,7 @@ public class ConnectivitySubsystemsRecoveryManager { checkIfAllSubsystemsRestartsAreDone(); } } - }; + } public ConnectivitySubsystemsRecoveryManager(@NonNull Context context, @NonNull Handler handler) { @@ -201,12 +204,12 @@ public class ConnectivitySubsystemsRecoveryManager { } private void startTrackingTelephonyRestart() { - mTelephonyManager.registerPhoneStateListener(new HandlerExecutor(mHandler), - mPhoneStateListener); + mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(mHandler), + mTelephonyCallback); } private void stopTrackingTelephonyRestart() { - mTelephonyManager.unregisterPhoneStateListener(mPhoneStateListener); + mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); } private void checkIfAllSubsystemsRestartsAreDone() { diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java index 228de039fc1b..35499c9b449a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java @@ -80,7 +80,8 @@ public class RecentLocationAccesses { * Fills a list of applications which queried location recently within specified time. * Apps are sorted by recency. Apps with more recent location accesses are in the front. */ - public List<Access> getAppList() { + @VisibleForTesting + List<Access> getAppList(boolean showSystemApps) { // Retrieve a location usage list from AppOps PackageManager pm = mContext.getPackageManager(); AppOpsManager aoManager = @@ -108,22 +109,26 @@ public class RecentLocationAccesses { // Don't show apps that do not have user sensitive location permissions boolean showApp = true; - for (int op : LOCATION_OPS) { - final String permission = AppOpsManager.opToPermission(op); - final int permissionFlags = pm.getPermissionFlags(permission, packageName, user); - if (PermissionChecker.checkPermissionForPreflight(mContext, permission, - PermissionChecker.PID_UNKNOWN, uid, packageName) - == PermissionChecker.PERMISSION_GRANTED) { - if ((permissionFlags - & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) { - showApp = false; - break; - } - } else { - if ((permissionFlags - & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { - showApp = false; - break; + if (!showSystemApps) { + for (int op : LOCATION_OPS) { + final String permission = AppOpsManager.opToPermission(op); + final int permissionFlags = pm.getPermissionFlags(permission, packageName, + user); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, packageName) + == PermissionChecker.PERMISSION_GRANTED) { + if ((permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) + == 0) { + showApp = false; + break; + } + } else { + if ((permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { + showApp = false; + break; + } } } } @@ -137,8 +142,15 @@ public class RecentLocationAccesses { return accesses; } - public List<Access> getAppListSorted() { - List<Access> accesses = getAppList(); + + /** + * Gets a list of apps that accessed location recently, sorting by recency. + * + * @param showSystemApps whether includes system apps in the list. + * @return the list of apps that recently accessed location. + */ + public List<Access> getAppListSorted(boolean showSystemApps) { + List<Access> accesses = getAppList(showSystemApps); // Sort the list of Access by recency. Most recent accesses first. Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() { @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java index 0cd5e4ded168..1a08366734bc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java @@ -17,11 +17,11 @@ package com.android.settingslib.mobile; import android.os.Handler; import android.os.Looper; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.util.Log; @@ -40,9 +40,9 @@ public class MobileStatusTracker { private final SubscriptionInfo mSubscriptionInfo; private final Callback mCallback; private final MobileStatus mMobileStatus; - private final PhoneStateListener mPhoneStateListener; private final SubscriptionDefaults mDefaults; private final Handler mReceiverHandler; + private final MobileTelephonyCallback mTelephonyCallback; /** * MobileStatusTracker constructors @@ -58,7 +58,7 @@ public class MobileStatusTracker { SubscriptionInfo info, SubscriptionDefaults defaults, Callback callback) { mPhone = phone; mReceiverHandler = new Handler(receiverLooper); - mPhoneStateListener = new MobilePhoneStateListener(); + mTelephonyCallback = new MobileTelephonyCallback(); mSubscriptionInfo = info; mDefaults = defaults; mCallback = callback; @@ -68,8 +68,8 @@ public class MobileStatusTracker { /* updateTelephony= */false, new MobileStatus(mMobileStatus))); } - public PhoneStateListener getPhoneStateListener() { - return mPhoneStateListener; + public MobileTelephonyCallback getTelephonyCallback() { + return mTelephonyCallback; } /** @@ -77,9 +77,9 @@ public class MobileStatusTracker { */ public void setListening(boolean listening) { if (listening) { - mPhone.registerPhoneStateListener(mReceiverHandler::post, mPhoneStateListener); + mPhone.registerTelephonyCallback(mReceiverHandler::post, mTelephonyCallback); } else { - mPhone.unregisterPhoneStateListener(mPhoneStateListener); + mPhone.unregisterTelephonyCallback(mTelephonyCallback); } } @@ -106,15 +106,14 @@ public class MobileStatusTracker { || activity == TelephonyManager.DATA_ACTIVITY_OUT; } - private class MobilePhoneStateListener extends PhoneStateListener implements - PhoneStateListener.ServiceStateChangedListener, - PhoneStateListener.SignalStrengthsChangedListener, - PhoneStateListener.CallStateChangedListener, - PhoneStateListener.DataConnectionStateChangedListener, - PhoneStateListener.DataActivityListener, - PhoneStateListener.CarrierNetworkChangeListener, - PhoneStateListener.ActiveDataSubscriptionIdChangedListener, - PhoneStateListener.DisplayInfoChangedListener{ + public class MobileTelephonyCallback extends TelephonyCallback implements + TelephonyCallback.ServiceStateListener, + TelephonyCallback.SignalStrengthsListener, + TelephonyCallback.DataConnectionStateListener, + TelephonyCallback.DataActivityListener, + TelephonyCallback.CarrierNetworkListener, + TelephonyCallback.ActiveDataSubscriptionIdListener, + TelephonyCallback.DisplayInfoListener{ @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java index ecd40667843e..f9584a3e15e9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java @@ -38,6 +38,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.ContactsContract.DisplayPhoto; import android.provider.MediaStore; +import android.util.EventLog; import android.util.Log; import android.view.Gravity; import android.view.View; @@ -126,6 +127,14 @@ public class EditUserPhotoController { } final Uri pictureUri = data != null && data.getData() != null ? data.getData() : mTakePictureUri; + + // Check if the result is a content uri + if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) { + Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme()); + EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath()); + return false; + } + switch (requestCode) { case REQUEST_CODE_CROP_PHOTO: onPhotoCropped(pictureUri); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index a81a05f1147a..303ee3c9fca2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -223,7 +223,8 @@ public class AccessPoint implements Comparable<AccessPoint> { public static final int SECURITY_OWE = 4; public static final int SECURITY_SAE = 5; public static final int SECURITY_EAP_SUITE_B = 6; - public static final int SECURITY_MAX_VAL = 7; // Has to be the last + public static final int SECURITY_EAP_WPA3_ENTERPRISE = 7; + public static final int SECURITY_MAX_VAL = 8; // Has to be the last private static final int PSK_UNKNOWN = 0; private static final int PSK_WPA = 1; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java index 245b7843110b..16d73a39d551 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java @@ -86,7 +86,7 @@ public class RecentLocationAccessesTest { @Test @Ignore public void testGetAppList_shouldFilterRecentAccesses() { - List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(); + List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(false); // Only two of the apps have requested location within 15 min. assertThat(requests).hasSize(2); // Make sure apps are ordered by recency @@ -115,7 +115,7 @@ public class RecentLocationAccessesTest { mockTestApplicationInfos( Process.SYSTEM_UID, RecentLocationAccesses.ANDROID_SYSTEM_PACKAGE_NAME); - List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(); + List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(true); // Android OS shouldn't show up in the list of apps. assertThat(requests).hasSize(2); // Make sure apps are ordered by recency diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 71e09106368b..1393116a814e 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -164,6 +164,7 @@ <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" /> <uses-permission android:name="android.permission.MANAGE_SEARCH_UI" /> <uses-permission android:name="android.permission.MANAGE_SMARTSPACE" /> + <uses-permission android:name="android.permission.MANAGE_UI_TRANSLATION" /> <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.SET_TIME" /> diff --git a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml index 6f91d770012a..edfc0aba5be7 100644 --- a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml +++ b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml @@ -20,6 +20,7 @@ Make the visibility to "gone" to prevent failures. --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/add_new_sound_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeightSmall" diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md index 60994d892b6e..ee8d02301d5d 100644 --- a/packages/SystemUI/README.md +++ b/packages/SystemUI/README.md @@ -144,10 +144,6 @@ Biometric UI. Delegates SysUI events to WM Shell controllers. -### [com.android.systemui.people.widget.PeopleSpaceWidgetEnabler](/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java) - -Enables People Space widgets. - --- * [Plugins](/packages/SystemUI/docs/plugins.md) diff --git a/packages/SystemUI/docs/media-controls-pipeline.png b/packages/SystemUI/docs/media-controls-pipeline.png Binary files differnew file mode 100644 index 000000000000..e7408ad9fd4a --- /dev/null +++ b/packages/SystemUI/docs/media-controls-pipeline.png diff --git a/packages/SystemUI/docs/media-controls.md b/packages/SystemUI/docs/media-controls.md new file mode 100644 index 000000000000..579f453a3a92 --- /dev/null +++ b/packages/SystemUI/docs/media-controls.md @@ -0,0 +1,94 @@ +# SysUI Media Controls Pipeline + +[TOC] + +## Purpose + +Describe how events flow through the media controls pipeline, and provide a high level overview of what the different components do. + +## Pipeline Diagram + + + +* Orange: External inputs +* Blue: Internal listeners; all except `MediaDataManager` and `ResumeMediaBrowser` implement [`MediaDataManager.Listener`](/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt#711) and receive `onMediaDataLoaded` and `onMediaDataRemoved` events + +## Classes + +Files under [`systemui/media/`](/packages/SystemUI/src/com/android/systemui/media/): + +* UI + * `dialog/` + * Output switcher dialog (maintained by Settings team) + * IlluminationDrawable.kt + * LightSourceDrawable.kt + * These create the glow animation when you tap on a button (see [`qs_media_light_source`](/packages/SystemUI/res/drawable/qs_media_light_source.xml)). Should be reusable in other layouts. + * Carousel: + * MediaCarouselController.kt + * Keeps the carousel view up to date and handles state changes (e.g. expansion) + * Handles settings gear and page indicator + * MediaCarouselScrollHandler.kt + * Handles scrolling between players in the carousel + * MediaScrollView.kt + * Scrollview used in the carousel layout, has some custom measurement code + * Individual players: + * KeyguardMediaController.kt + * Lockscreen media controls have a special wrapper in order to work with the existing lockscreen notification layout + * MediaControlPanel.java + * Main class for media control UI + * SeekBarObserver.kt + * Updates seekbar state + * SeekBarViewModel.kt + * Implements its own `computePosition()` for the seekbar (to avoid continually polling the `PlaybackState`, which involves binder calls) + * Does some touch falsing (ignore flings, require drags to start near the thumb - otherwise users would often accidentally trigger the seekbar when they meant to move the carousel or shade) + * PlayerViewHolder.kt + * Holds references to the UI elements in the panel +* Animation support: + * MediaHierarchyManager.kt + * Responsible for placement of media view and animation between hosts + * MediaHost.kt + * Every location that a media player could be located needs a `MediaHost` + * Tracks configuration (if it should show inactive media, needs falsing, etc.) + * MediaHostStatesManager.kt + * Manages the various media host states and coordinates heights between different players + * Has the most up to date state for any location + * MediaViewController.kt + * Controls a single instance of a media player, keeps the media view states up to date +* Backend + * MediaData.kt + * Holds all the media data (track info, active/resume state, etc.) + * MediaDataCombineLatest.kt + * Combines update events from `MediaDataManager` and `MediaDeviceManager`, so that downstream listeners will have device info + * MediaDataFilter.kt + * Filters media data based on the current user + * Exit point for the pipeline: "external listeners" (currently `MediaHost` and `MediaCarouselController`), while they should be added via `MediaDataManager.addListener()`, will actually be listening to this output + * MediaDataManager.kt + * Entry point for the pipeline; initializes listener connections and assigns external listeners to the correct exit point + * Converts media notifications and resumable media info into `MediaData` + * MediaDeviceManager.kt + * Handles device updates + * MediaFeatureFlag.kt + * Utility to check whether media controls are enabled + * MediaSessionBasedFilter.kt + * Filters media events based on media session. This prevents duplicate controls in situations like casting where we might get both a local and remote object for the same media session. + * MediaTimeoutListener.kt + * Listens to `PlaybackState` and marks controls inactive after the media has been paused/stopped for 10 minutes (value can be adjusted locally with `adb shell setprop debug.sysui.media_timeout [ms]`) + * MediaResumeListener.kt + * Listens for new media data and attempts to find a valid `MediaBrowserService` for the app. If successful, sends the information back to the `MediaDataManager` + * Saves up to 5 valid `MediaBrowserService` components found this way, and queries them for recent media on boot or user change + * Note: the user can disable this feature completely (or block certain apps from being resumable) in [Settings](https://source.corp.google.com/android/packages/apps/Settings/src/com/android/settings/sound/ResumableMediaAppsController.java), in which case this listener will do nothing (or ignore updates from the blocked apps). + * ResumeMediaBrowser.java + * Connects to an app's [`MediaBrowser`](https://developer.android.com/reference/android/media/browse/MediaBrowser) to determine whether SystemUI is able to connect and find a recent [`MediaItem`](https://developer.android.com/reference/android/media/browse/MediaBrowser.MediaItem) +* Factory classes (for unit testing): + * LocalMediaManagerFactory.kt + * MediaBrowserFactory.java + * MediaControllerFactory.java + * ResumeMediaBrowserFactory.java + +## Miscellaneous + +Other useful documents: + +* [go/sysui-media-resumption-requirements](https://goto.google.com/sysui-media-resumption-requirements) - Internal documentation for app developers about how to work with media resumption +* [Playing nicely with media controls](https://android-developers.googleblog.com/2020/08/playing-nicely-with-media-controls.html) - blog post on the Android 11 updates +* [Media Controls developer guide](https://developer.android.com/guide/topics/media/media-controls) diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml index 79868093fb12..71cdaf5c7091 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml @@ -24,7 +24,7 @@ <include style="@style/BouncerSecurityContainer" layout="@layout/keyguard_host_view" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" /> </FrameLayout> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml index 04e645bd0a32..1e142eaeef86 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml @@ -41,13 +41,14 @@ android:layout_gravity="center"> <com.android.keyguard.KeyguardSecurityViewFlipper android:id="@+id/view_flipper" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:paddingTop="@dimen/keyguard_security_view_top_margin" android:paddingStart="@dimen/keyguard_security_view_lateral_margin" android:paddingEnd="@dimen/keyguard_security_view_lateral_margin" + android:layout_gravity="center" android:gravity="center"> </com.android.keyguard.KeyguardSecurityViewFlipper> </com.android.keyguard.KeyguardSecurityContainer> diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml new file mode 100644 index 000000000000..e09bf7e37ed0 --- /dev/null +++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <bool name="can_use_one_handed_bouncer">true</bool> +</resources> diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index 8d9d6ee68c67..6176f7c1dd0a 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -22,4 +22,5 @@ <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] --> <bool name="config_disableMenuKeyInLockScreen">false</bool> + <bool name="can_use_one_handed_bouncer">false</bool> </resources> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 187ae58c2f2c..cf9de5e7e226 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -85,10 +85,10 @@ android:tint="?attr/wallpaperTextColor" /> <ImageView - android:id="@+id/alt_left_button" + android:id="@+id/wallet_button" android:layout_height="@dimen/keyguard_affordance_height" android:layout_width="@dimen/keyguard_affordance_width" - android:layout_gravity="bottom|start" + android:layout_gravity="bottom|end" android:scaleType="center" android:tint="?attr/wallpaperTextColor" android:layout_marginStart="24dp" diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index e2f3e2a306e3..7ba28a8483c3 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -76,7 +76,6 @@ android:layout_height="wrap_content" android:layout_marginBottom="42dp" android:layout_marginHorizontal="48dp" - android:adjustViewBounds="true" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@id/guideline" @@ -110,6 +109,7 @@ android:visibility="invisible" android:layout_width="200dp" android:layout_height="200dp" + android:elevation="2dp" app:layout_constraintTop_toBottomOf="@id/guideline" app:layout_constraintLeft_toLeftOf="parent" app:handleThickness="@dimen/screenshot_crop_handle_thickness" diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 13572fa9f4f3..db712e4b9f7a 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -17,6 +17,7 @@ <com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minHeight="48dp" android:clickable="true" android:paddingBottom="@dimen/qs_tile_padding_top" android:paddingTop="@dimen/qs_tile_padding_top" diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/packages/SystemUI/res/layout/udfps_animation_view_bp.xml index 380dd855ffe1..0cfbf2e61dd1 100644 --- a/packages/SystemUI/res/layout/udfps_animation_view.xml +++ b/packages/SystemUI/res/layout/udfps_animation_view_bp.xml @@ -14,8 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.UdfpsAnimationView +<com.android.systemui.biometrics.UdfpsAnimationViewBp xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/udfps_animation_view" android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_height="match_parent"> +</com.android.systemui.biometrics.UdfpsAnimationViewBp> diff --git a/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml b/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml new file mode 100644 index 000000000000..9b5752d2de59 --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml @@ -0,0 +1,34 @@ +<?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.systemui.biometrics.UdfpsAnimationViewEnroll + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- Enrollment progress bar--> + <com.android.systemui.biometrics.UdfpsProgressBar + android:id="@+id/progress_bar" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:max="100" + android:padding="@dimen/udfps_enroll_progress_thickness" + android:progress="0" + android:layout_gravity="center" + android:visibility="gone"/> + +</com.android.systemui.biometrics.UdfpsAnimationViewEnroll> diff --git a/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml b/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml new file mode 100644 index 000000000000..f32faa0df867 --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml @@ -0,0 +1,22 @@ +<?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.systemui.biometrics.UdfpsAnimationViewFpmOther + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> +</com.android.systemui.biometrics.UdfpsAnimationViewFpmOther> diff --git a/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml new file mode 100644 index 000000000000..644d1adac46b --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml @@ -0,0 +1,22 @@ +<?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.systemui.biometrics.UdfpsAnimationViewKeyguard + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> +</com.android.systemui.biometrics.UdfpsAnimationViewKeyguard> diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml index 6ae306e17209..e24c9e99a405 100644 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ b/packages/SystemUI/res/layout/udfps_view.xml @@ -22,15 +22,10 @@ android:layout_height="match_parent" systemui:sensorTouchAreaCoefficient="0.5"> - <!-- Enrollment progress bar--> - <com.android.systemui.biometrics.UdfpsProgressBar - android:id="@+id/progress_bar" + <com.android.systemui.biometrics.UdfpsSurfaceView + android:id="@+id/hbm_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:max="100" - android:padding="@dimen/udfps_enroll_progress_thickness" - android:progress="0" - android:layout_gravity="center" - android:visibility="gone"/> + android:visibility="invisible"/> </com.android.systemui.biometrics.UdfpsView> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 3bc1c8053db3..b3fca366ea1e 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -183,7 +183,9 @@ <color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 --> <!-- UDFPS colors --> - <color name="udfps_enroll_icon">#000000</color> <!-- 100% black --> + <color name="udfps_enroll_icon">#000000</color> <!-- 100% black --> + <color name="udfps_moving_target_fill">#cc4285f4</color> <!-- 80% blue --> + <color name="udfps_moving_target_stroke">#ff669DF6</color> <!-- 100% blue --> <!-- Logout button --> <color name="logout_button_bg_color">#ccffffff</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 0893c1488005..b75a0bc1525a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -91,9 +91,6 @@ <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> - <!-- The number of columns in the Quick Settings customizer --> - <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer> - <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> @@ -315,7 +312,6 @@ <item>com.android.systemui.accessibility.SystemActions</item> <item>com.android.systemui.toast.ToastUI</item> <item>com.android.systemui.wmshell.WMShell</item> - <item>com.android.systemui.people.widget.PeopleSpaceWidgetEnabler</item> </string-array> <!-- QS tile shape store width. negative implies fill configuration instead of stroke--> @@ -416,6 +412,9 @@ <!-- Whether or not child notifications that are part of a group will have shadows. --> <bool name="config_enableShadowOnChildNotifications">true</bool> + <!-- If true, group numbers are shown in the expander instead of via "+N" overflow number --> + <bool name="config_showNotificationGroupCountInExpander">true</bool> + <!-- Whether or not a view containing child notifications will have a custom background when it has been expanded to reveal its children. --> <bool name="config_showGroupNotificationBgWhenExpanded">false</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 5f8df5a13ad1..b5ded012b3bd 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -294,8 +294,10 @@ <string name="screenrecord_share_label">Share</string> <!-- A toast message shown after successfully canceling a screen recording [CHAR LIMIT=NONE] --> <string name="screenrecord_cancel_success">Screen recording canceled</string> - <!-- Notification text shown after saving a screen recording to prompt the user to view it [CHAR LIMIT=100] --> - <string name="screenrecord_save_message">Screen recording saved, tap to view</string> + <!-- Notification text shown after saving a screen recording [CHAR LIMIT=100] --> + <string name="screenrecord_save_title">Screen recording saved</string> + <!-- Subtext for a notification shown after saving a screen recording to prompt the user to view it [CHAR_LIMIT=100] --> + <string name="screenrecord_save_text">Tap to view</string> <!-- A toast message shown when there is an error deleting a screen recording [CHAR LIMIT=NONE] --> <string name="screenrecord_delete_error">Error deleting screen recording</string> <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 2d202fb45bbc..ec26b8d7a6a8 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -608,6 +608,11 @@ <item name="android:windowCloseOnTouchOutside">true</item> </style> + <!-- Privacy dialog --> + <style name="PrivacyDialog" parent="ScreenRecord"> + <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> + </style> + <!-- USB Contaminant dialog --> <style name ="USBContaminant" /> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl index e5ced3ec3dc2..54242bece2b6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl @@ -21,5 +21,5 @@ package com.android.systemui.shared.recents; */ oneway interface ISplitScreenListener { void onStagePositionChanged(int stage, int position); - void onTaskStageChanged(int taskId, int stage); -}
\ No newline at end of file + void onTaskStageChanged(int taskId, int stage, boolean visible); +} 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 5c943f63a9a1..bac4c43ccddc 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 @@ -19,6 +19,7 @@ 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; @@ -34,7 +35,7 @@ import com.android.systemui.shared.system.RemoteTransitionCompat; /** * Temporary callbacks into SystemUI. - * Next id = 30 + * Next id = 43 */ interface ISystemUiProxy { @@ -251,5 +252,7 @@ interface ISystemUiProxy { 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 int stage, in int position, in Bundle options) = 41; + in PendingIntent intent, in Intent fillInIntent, in int stage, in int position, + in Bundle options) = 41; + void removeFromSideStage(in int taskId) = 42; } 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 e6477f158c27..400bf15f8c65 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 @@ -113,17 +113,6 @@ public class RemoteAnimationAdapterCompat { // TODO(bc-unlock): Build wrapped object for non-apps target. final RemoteAnimationTargetCompat[] nonAppsCompat = new RemoteAnimationTargetCompat[0]; - final Runnable animationFinishedCallback = new Runnable() { - @Override - public void run() { - try { - finishCallback.onTransitionFinished(null /* wct */); - } catch (RemoteException e) { - Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" - + " finished callback", e); - } - } - }; // TODO(b/177438007): Move this set-up logic into launcher's animation impl. boolean isReturnToHome = false; @@ -143,8 +132,8 @@ public class RemoteAnimationAdapterCompat { final TransitionInfo.Change change = info.getChanges().get(i); final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); - // Only deal with roots - if (change.getParent() != null) continue; + // Only deal with independent layers + if (!TransitionInfo.isIndependent(change, info)) continue; if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { t.setLayer(leash, info.getChanges().size() * 3 - i); } @@ -156,6 +145,18 @@ public class RemoteAnimationAdapterCompat { } } t.apply(); + + final Runnable animationFinishedCallback = new Runnable() { + @Override + public void run() { + try { + finishCallback.onTransitionFinished(null /* wct */); + } catch (RemoteException e) { + Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + + " finished callback", e); + } + } + }; // TODO(bc-unlcok): Pass correct transit type. remoteAnimationAdapter.onAnimationStart( TRANSIT_OLD_NONE, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 5f6fd30ffa1b..a2d7707a1569 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -29,12 +29,17 @@ import android.app.AlertDialog; import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; +import android.provider.Settings; import android.util.AttributeSet; import android.util.MathUtils; import android.util.TypedValue; +import android.view.Gravity; import android.view.MotionEvent; +import android.view.OrientationEventListener; import android.view.VelocityTracker; +import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewPropertyAnimator; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimationControlListener; @@ -55,6 +60,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.List; @@ -99,6 +105,12 @@ public class KeyguardSecurityContainer extends FrameLayout { private boolean mDisappearAnimRunning; private SwipeListener mSwipeListener; + private boolean mIsSecurityViewLeftAligned = true; + private boolean mOneHandedMode = false; + private SecurityMode mSecurityMode = SecurityMode.Invalid; + private ViewPropertyAnimator mRunningOneHandedAnimator; + private final OrientationEventListener mOrientationEventListener; + private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -157,16 +169,20 @@ public class KeyguardSecurityContainer extends FrameLayout { // Used to notify the container when something interesting happens. public interface SecurityCallback { boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen); + void userActivity(); + void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); /** - * @param strongAuth wheher the user has authenticated with strong authentication like - * pattern, password or PIN but not by trust agents or fingerprint + * @param strongAuth wheher the user has authenticated with strong authentication like + * pattern, password or PIN but not by trust agents or fingerprint * @param targetUserId a user that needs to be the foreground user at the finish completion. */ void finish(boolean strongAuth, int targetUserId); + void reset(); + void onCancelClicked(); } @@ -224,12 +240,136 @@ public class KeyguardSecurityContainer extends FrameLayout { super(context, attrs, defStyle); mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); mViewConfiguration = ViewConfiguration.get(context); + + mOrientationEventListener = new OrientationEventListener(context) { + @Override + public void onOrientationChanged(int orientation) { + updateLayoutForSecurityMode(mSecurityMode); + } + }; } void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { + mSecurityMode = securityMode; mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); updateBiometricRetry(securityMode, faceAuthEnabled); + updateLayoutForSecurityMode(securityMode); + mOrientationEventListener.enable(); + } + + void updateLayoutForSecurityMode(SecurityMode securityMode) { + mSecurityMode = securityMode; + mOneHandedMode = canUseOneHandedBouncer(); + + if (mOneHandedMode) { + mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext); + } + + updateSecurityViewGravity(); + updateSecurityViewLocation(false); + } + + /** Return whether the one-handed keyguard should be enabled. */ + private boolean canUseOneHandedBouncer() { + // Is it enabled? + if (!getResources().getBoolean( + com.android.internal.R.bool.config_enableOneHandedKeyguard)) { + return false; + } + + if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) { + return false; + } + + return getResources().getBoolean(R.bool.can_use_one_handed_bouncer); + } + + /** Read whether the one-handed keyguard should be on the left/right from settings. */ + private boolean isOneHandedKeyguardLeftAligned(Context context) { + try { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ONE_HANDED_KEYGUARD_SIDE) + == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; + } catch (Settings.SettingNotFoundException ex) { + return true; + } + } + + private void updateSecurityViewGravity() { + View securityView = findKeyguardSecurityView(); + + if (securityView == null) { + return; + } + + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams(); + + if (mOneHandedMode) { + lp.gravity = Gravity.LEFT | Gravity.BOTTOM; + } else { + lp.gravity = Gravity.CENTER_HORIZONTAL; + } + + securityView.setLayoutParams(lp); + } + + /** + * Moves the inner security view to the correct location (in one handed mode) with animation. + * This is triggered when the user taps on the side of the screen that is not currently occupied + * by the security view . + */ + private void updateSecurityViewLocation(boolean animate) { + View securityView = findKeyguardSecurityView(); + + if (securityView == null) { + return; + } + + if (!mOneHandedMode) { + securityView.setTranslationX(0); + return; + } + + if (mRunningOneHandedAnimator != null) { + mRunningOneHandedAnimator.cancel(); + mRunningOneHandedAnimator = null; + } + + int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f); + + if (animate) { + mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation); + mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningOneHandedAnimator = null; + } + }); + + mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + mRunningOneHandedAnimator.start(); + } else { + securityView.setTranslationX(targetTranslation); + } + } + + @Nullable + private KeyguardSecurityViewFlipper findKeyguardSecurityView() { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + + if (isKeyguardSecurityView(child)) { + return (KeyguardSecurityViewFlipper) child; + } + } + + return null; + } + + private boolean isKeyguardSecurityView(View view) { + return view instanceof KeyguardSecurityViewFlipper; } public void onPause() { @@ -238,6 +378,7 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog = null; } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); + mOrientationEventListener.disable(); } @Override @@ -319,19 +460,44 @@ public class KeyguardSecurityContainer extends FrameLayout { if (mSwipeListener != null) { mSwipeListener.onSwipeUp(); } + } else { + if (!mIsDragging) { + handleTap(event); + } } } return true; } + private void handleTap(MotionEvent event) { + // If we're using a fullscreen security mode, skip + if (!mOneHandedMode) { + return; + } + + // Did the tap hit the "other" side of the bouncer? + if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f)) + || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) { + mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned; + + Settings.Global.putInt( + mContext.getContentResolver(), + Settings.Global.ONE_HANDED_KEYGUARD_SIDE, + mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT + : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); + + updateSecurityViewLocation(true); + } + } + void setSwipeListener(SwipeListener swipeListener) { mSwipeListener = swipeListener; } private void startSpringAnimation(float startVelocity) { mSpringAnimation - .setStartVelocity(startVelocity) - .animateToFinalPosition(0); + .setStartVelocity(startVelocity) + .animateToFinalPosition(0); } public void startDisappearAnimation(SecurityMode securitySelection) { @@ -441,18 +607,17 @@ public class KeyguardSecurityContainer extends FrameLayout { return insets.inset(0, 0, 0, inset); } - private void showDialog(String title, String message) { if (mAlertDialog != null) { mAlertDialog.dismiss(); } mAlertDialog = new AlertDialog.Builder(mContext) - .setTitle(title) - .setMessage(message) - .setCancelable(false) - .setNeutralButton(R.string.ok, null) - .create(); + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setNeutralButton(R.string.ok, null) + .create(); if (!(mContext instanceof Activity)) { mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); } @@ -490,6 +655,47 @@ public class KeyguardSecurityContainer extends FrameLayout { } } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthMeasureSpec) / 2, + MeasureSpec.getMode(widthMeasureSpec)); + + for (int i = 0; i < getChildCount(); i++) { + final View view = getChildAt(i); + if (view.getVisibility() != GONE) { + if (mOneHandedMode && isKeyguardSecurityView(view)) { + measureChildWithMargins(view, halfWidthMeasureSpec, 0, + heightMeasureSpec, 0); + } else { + measureChildWithMargins(view, widthMeasureSpec, 0, + heightMeasureSpec, 0); + } + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + maxWidth = Math.max(maxWidth, + view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, view.getMeasuredState()); + } + } + + maxWidth += getPaddingLeft() + getPaddingRight(); + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against our minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + } + void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { String message = null; switch (userType) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 1a8d420fb394..fdab8db67431 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -404,6 +404,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (newView != null) { newView.onResume(KeyguardSecurityView.VIEW_REVEALED); mSecurityViewFlipperController.show(newView); + mView.updateLayoutForSecurityMode(securityMode); } mSecurityCallback.onSecurityModeChanged( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java index c77c86711abf..631c24844417 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java @@ -92,4 +92,13 @@ public class KeyguardSecurityModel { throw new IllegalStateException("Unknown security quality:" + security); } } + + /** + * Returns whether the given security view should be used in a "one handed" way. This can be + * used to change how the security view is drawn (e.g. take up less of the screen, and align to + * one side). + */ + public static boolean isSecurityViewOneHanded(SecurityMode securityMode) { + return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 2373d75cd4ea..83c2d1e7f684 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -405,14 +405,19 @@ public class KeyguardSliceView extends LinearLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ public void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; - boolean wasAwake = mDarkAmount != 0; - if (isAwake == wasAwake) { + boolean isDozing = darkAmount != 0; + boolean wasDozing = mDarkAmount != 0; + if (isDozing == wasDozing) { return; } mDarkAmount = darkAmount; - setLayoutAnimationListener(isAwake ? null : mKeepAwakeListener); + setLayoutAnimationListener(isDozing ? null : mKeepAwakeListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index fe0ae33d17f1..ae4c8e5a3327 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -59,6 +59,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; import com.android.systemui.privacy.PrivacyItemController; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.screenrecord.RecordingController; @@ -246,6 +247,7 @@ public class Dependency { @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor; @Inject Lazy<BatteryController> mBatteryController; @Inject Lazy<NightDisplayListener> mNightDisplayListener; + @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController; @Inject Lazy<ManagedProfileController> mManagedProfileController; @Inject Lazy<NextAlarmController> mNextAlarmController; @Inject Lazy<DataSaverController> mDataSaverController; @@ -393,6 +395,8 @@ public class Dependency { mProviders.put(NightDisplayListener.class, mNightDisplayListener::get); + mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get); + mProviders.put(ManagedProfileController.class, mManagedProfileController::get); mProviders.put(NextAlarmController.class, mNextAlarmController::get); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 865ca40b1f4c..59c0fb816a96 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -19,15 +19,18 @@ package com.android.systemui; import android.app.ActivityThread; import android.app.Application; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Process; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.util.TimingsTraceLog; import android.view.SurfaceControl; @@ -37,6 +40,8 @@ import com.android.systemui.dagger.ContextComponentHelper; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; +import com.android.systemui.people.PeopleSpaceActivity; +import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import com.android.systemui.shared.system.ThreadedRendererCompat; import com.android.systemui.util.NotificationChannels; @@ -121,6 +126,26 @@ public class SystemUIApplication extends Application implements mServices[i].onBootCompleted(); } } + // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider. + // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto) + try { + int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.SHOW_PEOPLE_SPACE, 1); + context.getPackageManager().setComponentEnabledSetting( + new ComponentName(context, PeopleSpaceWidgetProvider.class), + showPeopleSpace == 1 + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + context.getPackageManager().setComponentEnabledSetting( + new ComponentName(context, PeopleSpaceActivity.class), + showPeopleSpace == 1 + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } catch (Exception e) { + Log.w(TAG, "Error enabling People Space widget:", e); + } } }, bootCompletedFilter); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 19520df08757..9d00262436e5 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -115,7 +115,8 @@ public class SystemUIFactory { .setShellCommandHandler(mWMComponent.getShellCommandHandler()) .setAppPairs(mWMComponent.getAppPairs()) .setTaskViewFactory(mWMComponent.getTaskViewFactory()) - .setTransitions(mWMComponent.getTransitions()); + .setTransitions(mWMComponent.getTransitions()) + .setStartingSurface(mWMComponent.getStartingSurface()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -129,7 +130,8 @@ public class SystemUIFactory { .setShellCommandHandler(Optional.ofNullable(null)) .setAppPairs(Optional.ofNullable(null)) .setTaskViewFactory(Optional.ofNullable(null)) - .setTransitions(Transitions.createEmptyForTesting()); + .setTransitions(Transitions.createEmptyForTesting()) + .setStartingSurface(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (initializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java index 3bf75d105b9f..a51b6fd16445 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import android.content.Context; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.view.View; @@ -31,24 +32,32 @@ import com.android.systemui.R; * sensor area. */ public abstract class UdfpsAnimation extends Drawable { - abstract void updateColor(); + protected abstract void updateColor(); + protected abstract void onDestroy(); @NonNull protected final Context mContext; @NonNull protected final Drawable mFingerprintDrawable; @Nullable private View mView; + private boolean mIlluminationShowing; public UdfpsAnimation(@NonNull Context context) { mContext = context; mFingerprintDrawable = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); + mFingerprintDrawable.mutate(); } public void onSensorRectUpdated(@NonNull RectF sensorRect) { - int margin = (int) (sensorRect.bottom - sensorRect.top) / 5; - mFingerprintDrawable.setBounds( - (int) sensorRect.left + margin, + final int margin = (int) sensorRect.height() / 8; + + final Rect bounds = new Rect((int) sensorRect.left + margin, (int) sensorRect.top + margin, (int) sensorRect.right - margin, (int) sensorRect.bottom - margin); + updateFingerprintIconBounds(bounds); + } + + protected void updateFingerprintIconBounds(@NonNull Rect bounds) { + mFingerprintDrawable.setBounds(bounds); } @Override @@ -60,6 +69,14 @@ public abstract class UdfpsAnimation extends Drawable { mView = view; } + boolean isIlluminationShowing() { + return mIlluminationShowing; + } + + void setIlluminationShowing(boolean showing) { + mIlluminationShowing = showing; + } + /** * @return The amount of padding that's needed on each side of the sensor, in pixels. */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java index 5290986b2a1c..015a598e972b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java @@ -22,13 +22,14 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; -import android.util.Log; +import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.settingslib.Utils; import com.android.systemui.R; /** @@ -40,9 +41,13 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { private static final float SHADOW_RADIUS = 5.f; private static final float PROGRESS_BAR_RADIUS = 140.f; - @Nullable private RectF mSensorRect; + @NonNull private final Drawable mMovingTargetFpIcon; @NonNull private final Paint mSensorPaint; - private final int mNotificationShadeColor; + @NonNull private final Paint mBlueFill; + @NonNull private final Paint mBlueStroke;; + + @Nullable private RectF mSensorRect; + @Nullable private UdfpsEnrollHelper mEnrollHelper; UdfpsAnimationEnroll(@NonNull Context context) { super(context); @@ -53,23 +58,54 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK); mSensorPaint.setStyle(Paint.Style.FILL); - mNotificationShadeColor = Utils.getColorAttr(context, - android.R.attr.colorBackgroundFloating).getDefaultColor(); + mBlueFill = new Paint(0 /* flags */); + mBlueFill.setAntiAlias(true); + mBlueFill.setColor(context.getColor(R.color.udfps_moving_target_fill)); + mBlueFill.setStyle(Paint.Style.FILL); + + mBlueStroke = new Paint(0 /* flags */); + mBlueStroke.setAntiAlias(true); + mBlueStroke.setColor(context.getColor(R.color.udfps_moving_target_stroke)); + mBlueStroke.setStyle(Paint.Style.STROKE); + mBlueStroke.setStrokeWidth(12); + + mMovingTargetFpIcon = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); + mMovingTargetFpIcon.setTint(Color.WHITE); + mMovingTargetFpIcon.mutate(); + } + + void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { + mEnrollHelper = helper; } @Override - void updateColor() { + protected void updateColor() { mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); } @Override + protected void onDestroy() { + + } + + @Override public void onSensorRectUpdated(@NonNull RectF sensorRect) { super.onSensorRectUpdated(sensorRect); mSensorRect = sensorRect; } @Override + protected void updateFingerprintIconBounds(@NonNull Rect bounds) { + super.updateFingerprintIconBounds(bounds); + mMovingTargetFpIcon.setBounds(bounds); + } + + @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + final boolean isNightMode = (mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) != 0; if (!isNightMode) { @@ -78,6 +114,24 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { } } mFingerprintDrawable.draw(canvas); + + // Draw moving target + if (mEnrollHelper.isCenterEnrollmentComplete()) { + mFingerprintDrawable.setAlpha(64); + + canvas.save(); + final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint(); + canvas.translate(point.x, point.y); + if (mSensorRect != null) { + canvas.drawOval(mSensorRect, mBlueFill); + canvas.drawOval(mSensorRect, mBlueStroke); + } + + mMovingTargetFpIcon.draw(canvas); + canvas.restore(); + } else { + mFingerprintDrawable.setAlpha(255); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java index efc864ade5ff..ef7a34000841 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java @@ -34,12 +34,21 @@ public class UdfpsAnimationFpmOther extends UdfpsAnimation { } @Override - void updateColor() { + protected void updateColor() { + + } + + @Override + protected void onDestroy() { } @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + mFingerprintDrawable.draw(canvas); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java index 8664e44c9ad2..5f268cfa8fa5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java @@ -42,6 +42,7 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv private static final String TAG = "UdfpsAnimationKeyguard"; @NonNull private final Context mContext; + @NonNull private final StatusBarStateController mStatusBarStateController; private final int mMaxBurnInOffsetX; private final int mMaxBurnInOffsetY; @@ -54,6 +55,7 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv @NonNull StatusBarStateController statusBarStateController) { super(context); mContext = context; + mStatusBarStateController = statusBarStateController; mMaxBurnInOffsetX = context.getResources() .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); @@ -89,6 +91,10 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv @Override public void draw(@NonNull Canvas canvas) { + if (isIlluminationShowing()) { + return; + } + canvas.save(); canvas.translate(mBurnInOffsetX, mBurnInOffsetY); mFingerprintDrawable.draw(canvas); @@ -106,11 +112,16 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv } @Override - public void updateColor() { + protected void updateColor() { final int lockScreenIconColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); final int ambientDisplayIconColor = Color.WHITE; mFingerprintDrawable.setTint(ColorUtils.blendARGB(lockScreenIconColor, ambientDisplayIconColor, mInterpolatedDarkAmount)); } + + @Override + protected void onDestroy() { + mStatusBarStateController.removeCallback(this); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index 44122cba8716..43ecf6778022 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -20,40 +20,52 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Canvas; +import android.graphics.PointF; import android.graphics.RectF; import android.util.AttributeSet; -import android.view.View; +import android.widget.FrameLayout; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.statusbar.phone.StatusBar; /** - * Class that coordinates non-HBM animations (such as enroll, keyguard, BiometricPrompt, - * FingerprintManager). + * Base class for views containing UDFPS animations. Note that this is a FrameLayout so that we + * can support multiple child views drawing on the same region around the sensor location. */ -public class UdfpsAnimationView extends View implements DozeReceiver, +public abstract class UdfpsAnimationView extends FrameLayout implements DozeReceiver, StatusBar.ExpansionChangedListener { private static final String TAG = "UdfpsAnimationView"; + @Nullable protected abstract UdfpsAnimation getUdfpsAnimation(); + @NonNull private UdfpsView mParent; - @Nullable private UdfpsAnimation mUdfpsAnimation; @NonNull private RectF mSensorRect; private int mAlpha; public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mSensorRect = new RectF(); + setWillNotDraw(false); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - if (mUdfpsAnimation != null) { + if (getUdfpsAnimation() != null) { final int alpha = mParent.shouldPauseAuth() ? mAlpha : 255; - mUdfpsAnimation.setAlpha(alpha); - mUdfpsAnimation.draw(canvas); + getUdfpsAnimation().setAlpha(alpha); + getUdfpsAnimation().draw(canvas); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().onDestroy(); } } @@ -69,38 +81,46 @@ public class UdfpsAnimationView extends View implements DozeReceiver, return (int) ((1 - percent) * 255); } - void setParent(@NonNull UdfpsView parent) { - mParent = parent; + void onIlluminationStarting() { + if (getUdfpsAnimation() == null) { + return; + } + + getUdfpsAnimation().setIlluminationShowing(true); + postInvalidate(); } - void setAnimation(@Nullable UdfpsAnimation animation) { - if (mUdfpsAnimation != null) { - mUdfpsAnimation.setAnimationView(null); + void onIlluminationStopped() { + if (getUdfpsAnimation() == null) { + return; } - mUdfpsAnimation = animation; - if (mUdfpsAnimation != null) { - mUdfpsAnimation.setAnimationView(this); - } + getUdfpsAnimation().setIlluminationShowing(false); + postInvalidate(); + } + + void setParent(@NonNull UdfpsView parent) { + mParent = parent; } void onSensorRectUpdated(@NonNull RectF sensorRect) { mSensorRect = sensorRect; - if (mUdfpsAnimation != null) { - mUdfpsAnimation.onSensorRectUpdated(mSensorRect); + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().onSensorRectUpdated(mSensorRect); } } void updateColor() { - if (mUdfpsAnimation != null) { - mUdfpsAnimation.updateColor(); + if (getUdfpsAnimation() != null) { + getUdfpsAnimation().updateColor(); } + postInvalidate(); } @Override public void dozeTimeTick() { - if (mUdfpsAnimation instanceof DozeReceiver) { - ((DozeReceiver) mUdfpsAnimation).dozeTimeTick(); + if (getUdfpsAnimation() instanceof DozeReceiver) { + ((DozeReceiver) getUdfpsAnimation()).dozeTimeTick(); } } @@ -111,16 +131,26 @@ public class UdfpsAnimationView extends View implements DozeReceiver, } public int getPaddingX() { - if (mUdfpsAnimation == null) { + if (getUdfpsAnimation() == null) { return 0; } - return mUdfpsAnimation.getPaddingX(); + return getUdfpsAnimation().getPaddingX(); } public int getPaddingY() { - if (mUdfpsAnimation == null) { + if (getUdfpsAnimation() == null) { return 0; } - return mUdfpsAnimation.getPaddingY(); + return getUdfpsAnimation().getPaddingY(); + } + + /** + * @return the amount of translation needed if the view currently requires the user to touch + * somewhere other than the exact center of the sensor. For example, this can happen + * during guided enrollment. + */ + @NonNull + PointF getTouchTranslation() { + return new PointF(0, 0); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java new file mode 100644 index 000000000000..515b442b61f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +/** + * Class that coordinates non-HBM animations during BiometricPrompt. + * + * Note that {@link AuthBiometricUdfpsView} also shows UDFPS animations. At some point we should + * de-dupe this if necessary. This will probably happen once the top-level TODO in UdfpsController + * is completed (inflate operation-specific views, instead of inflating generic udfps_view and + * adding operation-specific animations to it). + */ +public class UdfpsAnimationViewBp extends UdfpsAnimationView { + public UdfpsAnimationViewBp(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java new file mode 100644 index 000000000000..543df33dd5d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PointF; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.android.systemui.R; + +/** + * Class that coordinates non-HBM animations during enrollment. + */ +public class UdfpsAnimationViewEnroll extends UdfpsAnimationView + implements UdfpsEnrollHelper.Listener { + + private static final String TAG = "UdfpsAnimationViewEnroll"; + + @NonNull private UdfpsAnimationEnroll mUdfpsAnimation; + @NonNull private UdfpsProgressBar mProgressBar; + @Nullable private UdfpsEnrollHelper mEnrollHelper; + + @NonNull + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mUdfpsAnimation; + } + + public UdfpsAnimationViewEnroll(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mUdfpsAnimation = new UdfpsAnimationEnroll(context); + } + + public void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { + mEnrollHelper = helper; + mUdfpsAnimation.setEnrollHelper(helper); + } + + @Override + protected void onFinishInflate() { + mProgressBar = findViewById(R.id.progress_bar); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mEnrollHelper == null) { + Log.e(TAG, "Enroll helper is null"); + return; + } + + if (mEnrollHelper.shouldShowProgressBar()) { + mProgressBar.setVisibility(View.VISIBLE); + + // Only need enrollment updates if the progress bar is showing :) + mEnrollHelper.setListener(this); + } + } + + @Override + public void onEnrollmentProgress(int remaining, int totalSteps) { + final int interpolatedProgress = mProgressBar.getMax() + * Math.max(0, totalSteps + 1 - remaining) / (totalSteps + 1); + + mProgressBar.setProgress(interpolatedProgress, true); + } + + @NonNull + @Override + PointF getTouchTranslation() { + if (!mEnrollHelper.isCenterEnrollmentComplete()) { + return new PointF(0, 0); + } else { + return mEnrollHelper.getNextGuidedEnrollmentPoint(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java new file mode 100644 index 000000000000..3d2f5a0fe5cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +/** + * Class that coordinates non-HBM animations during other usage of FingerprintManager (not + * including Keyguard). + */ +public class UdfpsAnimationViewFpmOther extends UdfpsAnimationView { + + private final UdfpsAnimationFpmOther mAnimation; + + public UdfpsAnimationViewFpmOther(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mAnimation = new UdfpsAnimationFpmOther(context); + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mAnimation; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java new file mode 100644 index 000000000000..7d0b3e59feb1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; + +/** + * Class that coordinates non-HBM animations during keyguard authentication. + */ +public class UdfpsAnimationViewKeyguard extends UdfpsAnimationView { + @Nullable private UdfpsAnimationKeyguard mAnimation; + + public UdfpsAnimationViewKeyguard(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + void setStatusBarStateController(@NonNull StatusBarStateController statusBarStateController) { + if (mAnimation == null) { + mAnimation = new UdfpsAnimationKeyguard(getContext(), statusBarStateController); + mAnimation.setAnimationView(this); + } + } + + @Nullable + @Override + protected UdfpsAnimation getUdfpsAnimation() { + return mAnimation; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 71fba3302abc..852cdcbe20fd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -71,7 +71,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull private final LayoutInflater mInflater; private final WindowManager mWindowManager; private final DelayableExecutor mFgExecutor; - private final StatusBarStateController mStatusBarStateController; + @NonNull private final StatusBar mStatusBar; + @NonNull private final StatusBarStateController mStatusBarStateController; // 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; @@ -96,7 +97,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { public void showUdfpsOverlay(int sensorId, int reason) { if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) { - mEnrollHelper = new UdfpsEnrollHelper(reason); + mEnrollHelper = new UdfpsEnrollHelper(mContext, reason); } else { mEnrollHelper = null; } @@ -110,18 +111,20 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @Override public void onEnrollmentProgress(int sensorId, int remaining) { - if (mView == null) { + if (mEnrollHelper == null) { + Log.e(TAG, "onEnrollProgress received but helper is null"); return; } - mView.onEnrollmentProgress(remaining); + mEnrollHelper.onEnrollmentProgress(remaining); } @Override public void onEnrollmentHelp(int sensorId) { - if (mView == null) { + if (mEnrollHelper == null) { + Log.e(TAG, "onEnrollmentHelp received but helper is null"); return; } - mView.onEnrollmentHelp(); + mEnrollHelper.onEnrollmentHelp(); } @Override @@ -135,20 +138,14 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = - (expansion, expanded) -> { - if (mView != null) { - mView.onExpansionChanged(expansion, expanded); - } - }; + (expansion, expanded) -> mView.onExpansionChanged(expansion, expanded); @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override public void onStateChanged(int newState) { - if (mView != null) { mView.onStateChanged(newState); - } } }; @@ -189,7 +186,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { WindowManager windowManager, @NonNull StatusBarStateController statusBarStateController, @Main DelayableExecutor fgExecutor, - @Nullable StatusBar statusBar) { + @NonNull StatusBar statusBar) { mContext = context; mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the @@ -197,6 +194,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mFingerprintManager = checkNotNull(fingerprintManager); mWindowManager = windowManager; mFgExecutor = fgExecutor; + mStatusBar = statusBar; mStatusBarStateController = statusBarStateController; mSensorProps = findFirstUdfps(); @@ -217,9 +215,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; - statusBar.addExpansionChangedListener(mStatusBarExpansionListener); - mStatusBarStateController.addCallback(mStatusBarStateListener); - mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController()); } @@ -236,6 +231,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @Override public void dozeTimeTick() { + if (mView == null) { + return; + } mView.dozeTimeTick(); } @@ -278,7 +276,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } } - private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimation animation) { + private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimationView animation) { final int paddingX = animation != null ? animation.getPaddingX() : 0; final int paddingY = animation != null ? animation.getPaddingY() : 0; @@ -330,13 +328,19 @@ public class UdfpsController implements DozeReceiver, HbmCallback { if (mView == null) { try { Log.v(TAG, "showUdfpsOverlay | adding window"); - + // TODO: Eventually we should refactor the code to inflate an + // operation-specific view here, instead of inflating a generic udfps_view + // and adding operation-specific animations to it. mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false); mView.setSensorProperties(mSensorProps); mView.setHbmCallback(this); - final UdfpsAnimation animation = getUdfpsAnimationForReason(reason); - mView.setExtras(animation, mEnrollHelper); + final UdfpsAnimationView animation = getUdfpsAnimationViewForReason(reason); + mView.setAnimationView(animation); + + mStatusBar.addExpansionChangedListener(mStatusBarExpansionListener); + mStatusBarStateController.addCallback(mStatusBarStateListener); + mWindowManager.addView(mView, computeLayoutParams(animation)); mView.setOnTouchListener(mOnTouchListener); } catch (RuntimeException e) { @@ -348,17 +352,40 @@ public class UdfpsController implements DozeReceiver, HbmCallback { }); } - @Nullable - private UdfpsAnimation getUdfpsAnimationForReason(int reason) { + @NonNull + private UdfpsAnimationView getUdfpsAnimationViewForReason(int reason) { Log.d(TAG, "getUdfpsAnimationForReason: " + reason); + + final LayoutInflater inflater = LayoutInflater.from(mContext); + switch (reason) { case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR: - case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: - return new UdfpsAnimationEnroll(mContext); - case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: - return new UdfpsAnimationKeyguard(mContext, mStatusBarStateController); - case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: - return new UdfpsAnimationFpmOther(mContext); + case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: { + final UdfpsAnimationViewEnroll view = (UdfpsAnimationViewEnroll) + inflater.inflate(R.layout.udfps_animation_view_enroll, null, false); + view.setEnrollHelper(mEnrollHelper); + return view; + } + + case IUdfpsOverlayController.REASON_AUTH_BP: { + final UdfpsAnimationViewBp view = (UdfpsAnimationViewBp) + inflater.inflate(R.layout.udfps_animation_view_bp, null, false); + return view; + } + + case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: { + final UdfpsAnimationViewKeyguard view = (UdfpsAnimationViewKeyguard) + inflater.inflate(R.layout.udfps_animation_view_keyguard, null, false); + view.setStatusBarStateController(mStatusBarStateController); + return view; + } + + case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: { + final UdfpsAnimationViewFpmOther view = (UdfpsAnimationViewFpmOther) + inflater.inflate(R.layout.udfps_animation_view_fpm_other, null, false); + return view; + } + default: Log.d(TAG, "Animation for reason " + reason + " not supported yet"); return null; @@ -371,6 +398,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback { Log.v(TAG, "hideUdfpsOverlay | removing window"); // Reset the controller back to its starting state. onFingerUp(); + + mStatusBar.removeExpansionChangedListener(mStatusBarExpansionListener); + mStatusBarStateController.removeCallback(mStatusBarStateListener); + mWindowManager.removeView(mView); mView = null; } else { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 2442633a4a69..14eca1b1cb2c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -16,9 +16,15 @@ package com.android.systemui.biometrics; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PointF; import android.hardware.fingerprint.IUdfpsOverlayController; +import android.util.TypedValue; -import androidx.annotation.NonNull; +import java.util.ArrayList; +import java.util.List; /** * Helps keep track of enrollment state and animates the progress bar accordingly. @@ -26,35 +32,96 @@ import androidx.annotation.NonNull; public class UdfpsEnrollHelper { private static final String TAG = "UdfpsEnrollHelper"; + // Enroll with two center touches before going to guided enrollment + private static final int NUM_CENTER_TOUCHES = 2; + + interface Listener { + void onEnrollmentProgress(int remaining, int totalSteps); + } + // IUdfpsOverlayController reason private final int mEnrollReason; + private final List<PointF> mGuidedEnrollmentPoints; private int mTotalSteps = -1; - private int mCurrentProgress = 0; + private int mRemainingSteps = -1; + + // Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the + // interface makes no promises about monotonically increasing by one each time. + private int mLocationsEnrolled = 0; + + @Nullable Listener mListener; - public UdfpsEnrollHelper(int reason) { + public UdfpsEnrollHelper(@NonNull Context context, int reason) { mEnrollReason = reason; + mGuidedEnrollmentPoints = new ArrayList<>(); + + // Number of pixels per mm + float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1, + context.getResources().getDisplayMetrics()); + + mGuidedEnrollmentPoints.add(new PointF( 2.00f * px, 0.00f * px)); + mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px)); + mGuidedEnrollmentPoints.add(new PointF( 0.88f * px, 2.70f * px)); + mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px)); + mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.29f * px, 4.92f * px)); + mGuidedEnrollmentPoints.add(new PointF( 3.82f * px, 1.78f * px)); } boolean shouldShowProgressBar() { return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING; } - void onEnrollmentProgress(int remaining, @NonNull UdfpsProgressBar progressBar) { + void onEnrollmentProgress(int remaining) { if (mTotalSteps == -1) { mTotalSteps = remaining; } - mCurrentProgress = progressBar.getMax() * Math.max(0, mTotalSteps + 1 - remaining) - / (mTotalSteps + 1); - progressBar.setProgress(mCurrentProgress, true /* animate */); - } + if (remaining != mRemainingSteps) { + mLocationsEnrolled++; + } + + mRemainingSteps = remaining; - void updateProgress(@NonNull UdfpsProgressBar progressBar) { - progressBar.setProgress(mCurrentProgress); + if (mListener != null) { + mListener.onEnrollmentProgress(remaining, mTotalSteps); + } } void onEnrollmentHelp() { } + + void setListener(@NonNull Listener listener) { + mListener = listener; + + // Only notify during setListener if enrollment is already in progress, so the progress + // bar can be updated. If enrollment has not started yet, the progress bar will be empty + // anyway. + if (mTotalSteps != -1) { + mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps); + } + } + + boolean isCenterEnrollmentComplete() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + final int stepsEnrolled = mTotalSteps - mRemainingSteps; + return stepsEnrolled >= NUM_CENTER_TOUCHES; + } + + @NonNull + PointF getNextGuidedEnrollmentPoint() { + final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES; + return mGuidedEnrollmentPoints.get(index % mGuidedEnrollmentPoints.size()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java index 97c215e1d75f..61ec127ee946 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java @@ -52,6 +52,12 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { public UdfpsSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); + // Make this SurfaceView draw on top of everything else in this window. This allows us to + // 1) Always show the HBM circle on top of everything else, and + // 2) Properly composite this view with any other animations in the same window no matter + // what contents are added in which order to this view hierarchy. + setZOrderOnTop(true); + mHolder = getHolder(); mHolder.setFormat(PixelFormat.RGBA_8888); @@ -61,7 +67,9 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator { mSensorPaint.setARGB(255, 255, 255, 255); mSensorPaint.setStyle(Paint.Style.FILL); - mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint); + mIlluminationDotDrawable = canvas -> { + canvas.drawOval(mSensorRect, mSensorPaint); + }; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 399794391700..75a362131de1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -27,12 +27,12 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.PointF; import android.graphics.RectF; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; @@ -51,12 +51,11 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private static final int DEBUG_TEXT_SIZE_PX = 32; - @NonNull private final UdfpsSurfaceView mHbmSurfaceView; - @NonNull private final UdfpsAnimationView mAnimationView; @NonNull private final RectF mSensorRect; @NonNull private final Paint mDebugTextPaint; - @Nullable private UdfpsProgressBar mProgressBar; + @NonNull private UdfpsSurfaceView mHbmSurfaceView; + @Nullable private UdfpsAnimationView mAnimationView; // Used to obtain the sensor location. @NonNull private FingerprintSensorPropertiesInternal mSensorProps; @@ -66,7 +65,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private boolean mIlluminationRequested; private int mStatusBarState; private boolean mNotificationShadeExpanded; - @Nullable private UdfpsEnrollHelper mEnrollHelper; public UdfpsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -84,19 +82,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin a.recycle(); } - // Inflate UdfpsSurfaceView - final LayoutInflater inflater = LayoutInflater.from(context); - mHbmSurfaceView = (UdfpsSurfaceView) inflater.inflate(R.layout.udfps_surface_view, - null, false); - addView(mHbmSurfaceView); - mHbmSurfaceView.setVisibility(View.INVISIBLE); - - // Inflate UdfpsAnimationView - mAnimationView = (UdfpsAnimationView) inflater.inflate(R.layout.udfps_animation_view, - null, false); - mAnimationView.setParent(this); - addView(mAnimationView); - mSensorRect = new RectF(); mDebugTextPaint = new Paint(); @@ -107,22 +92,22 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin mIlluminationRequested = false; } + @Override + protected void onFinishInflate() { + mHbmSurfaceView = findViewById(R.id.hbm_view); + } + void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) { mSensorProps = properties; } - void setExtras(@Nullable UdfpsAnimation animation, @Nullable UdfpsEnrollHelper enrollHelper) { - mAnimationView.setAnimation(animation); - - mEnrollHelper = enrollHelper; + void setAnimationView(@NonNull UdfpsAnimationView animation) { + mAnimationView = animation; + animation.setParent(this); - if (enrollHelper != null) { - mEnrollHelper.updateProgress(mProgressBar); - mProgressBar.setVisibility(enrollHelper.shouldShowProgressBar() - ? View.VISIBLE : View.GONE); - } else { - mProgressBar.setVisibility(View.GONE); - } + // TODO: Consider using a ViewStub placeholder to maintain positioning and inflating it + // after the animation type has been decided. + addView(animation, 0); } @Override @@ -132,6 +117,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void dozeTimeTick() { + if (mAnimationView == null) { + return; + } mAnimationView.dozeTimeTick(); } @@ -143,12 +131,10 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void onExpansionChanged(float expansion, boolean expanded) { mNotificationShadeExpanded = expanded; - mAnimationView.onExpansionChanged(expansion, expanded); - } - @Override - protected void onFinishInflate() { - mProgressBar = findViewById(R.id.progress_bar); + if (mAnimationView != null) { + mAnimationView.onExpansionChanged(expansion, expanded); + } } @Override @@ -195,8 +181,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin boolean isValidTouch(float x, float y, float pressure) { // The X and Y coordinates of the sensor's center. - final float cx = mSensorRect.centerX(); - final float cy = mSensorRect.centerY(); + final PointF translation = mAnimationView.getTouchTranslation(); + final float cx = mSensorRect.centerX() + translation.x; + final float cy = mSensorRect.centerY() + translation.y; // Radii along the X and Y axes. final float rx = (mSensorRect.right - mSensorRect.left) / 2.0f; final float ry = (mSensorRect.bottom - mSensorRect.top) / 2.0f; @@ -229,7 +216,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void startIllumination(@Nullable Runnable onIlluminatedRunnable) { mIlluminationRequested = true; - mAnimationView.setVisibility(View.INVISIBLE); + mAnimationView.onIlluminationStarting(); mHbmSurfaceView.setVisibility(View.VISIBLE); mHbmSurfaceView.startIllumination(onIlluminatedRunnable); } @@ -237,16 +224,8 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void stopIllumination() { mIlluminationRequested = false; - mAnimationView.setVisibility(View.VISIBLE); + mAnimationView.onIlluminationStopped(); mHbmSurfaceView.setVisibility(View.INVISIBLE); mHbmSurfaceView.stopIllumination(); } - - void onEnrollmentProgress(int remaining) { - mEnrollHelper.onEnrollmentProgress(remaining, mProgressBar); - } - - void onEnrollmentHelp() { - - } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 726e2d06bf90..a2f96bbad203 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.om.OverlayManager; import android.hardware.display.AmbientDisplayConfiguration; +import android.hardware.display.ColorDisplayManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -60,6 +61,7 @@ import com.android.systemui.navigationbar.NavigationBarOverlayController; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.PluginInitializerImpl; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.UserTracker; @@ -82,6 +84,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.theme.ThemeOverlayApplier; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -266,6 +269,15 @@ public class DependencyProvider { } /** */ + @SysUISingleton + @Provides + public ReduceBrightColorsController provideReduceBrightColorsListener( + @Background Handler bgHandler, UserTracker userTracker, + ColorDisplayManager colorDisplayManager, SecureSettings secureSettings) { + return new ReduceBrightColorsController(userTracker, bgHandler, + colorDisplayManager, secureSettings); + } + @Provides @SysUISingleton public ActivityManagerWrapper provideActivityManagerWrapper() { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 04c4e977b2cf..1c5715c0296d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -25,6 +25,7 @@ import android.app.IActivityTaskManager; import android.app.IWallpaperManager; import android.app.KeyguardManager; import android.app.NotificationManager; +import android.app.StatsManager; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.app.role.RoleManager; @@ -321,6 +322,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static StatsManager provideStatsManager(Context context) { + return context.getSystemService(StatsManager.class); + } + + @Provides + @Singleton @Nullable static TelecomManager provideTelecomManager(Context context) { return context.getSystemService(TelecomManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index ffb8446f3e21..8f79de518419 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -33,6 +33,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; 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 java.util.Optional; @@ -88,6 +89,9 @@ public interface SysUIComponent { @BindsInstance Builder setTransitions(RemoteTransitions t); + @BindsInstance + Builder setStartingSurface(Optional<StartingSurface> s); + SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 5d226d562c29..1ed881993800 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -27,7 +27,6 @@ import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.media.systemsounds.HomeSoundEffectController; -import com.android.systemui.people.widget.PeopleSpaceWidgetEnabler; import com.android.systemui.power.PowerUI; import com.android.systemui.privacy.television.TvOngoingPrivacyChip; import com.android.systemui.recents.Recents; @@ -185,10 +184,4 @@ public abstract class SystemUIBinder { @IntoMap @ClassKey(HomeSoundEffectController.class) public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui); - - /** Inject into PeopleSpaceWidgetEnabler. */ - @Binds - @IntoMap - @ClassKey(PeopleSpaceWidgetEnabler.class) - public abstract SystemUI bindPeopleSpaceWidgetEnabler(PeopleSpaceWidgetEnabler sysui); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index f3726a37bb65..1b77d1c16639 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -32,6 +32,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; 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 java.util.Optional; @@ -98,4 +99,7 @@ public interface WMComponent { @WMSingleton RemoteTransitions getTransitions(); + + @WMSingleton + Optional<StartingSurface> getStartingSurface(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt index bd3f5a6d82a5..7fb7d8b0eaa5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt @@ -20,9 +20,13 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.text.TextUtils +import android.util.Log import com.android.settingslib.media.MediaOutputConstants import javax.inject.Inject +private const val TAG = "MediaOutputDlgReceiver" +private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + /** * BroadcastReceiver for handling media output intent */ @@ -32,8 +36,13 @@ class MediaOutputDialogReceiver @Inject constructor( override fun onReceive(context: Context, intent: Intent) { if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action)) { - mediaOutputDialogFactory.create( - intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME), false) + val packageName: String? = + intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME) + if (!TextUtils.isEmpty(packageName)) { + mediaOutputDialogFactory.create(packageName!!, false) + } else if (DEBUG) { + Log.e(TAG, "Unable to launch media output dialog. Package name is empty.") + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 70b7b047eebc..4491cc12a3cb 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -180,7 +180,7 @@ public class NavigationBarController implements Callbacks, } } else { for (int i = 0; i < mNavigationBars.size(); i++) { - mNavigationBars.get(i).onConfigurationChanged(newConfig); + mNavigationBars.valueAt(i).onConfigurationChanged(newConfig); } } } @@ -193,7 +193,7 @@ public class NavigationBarController implements Callbacks, if (navBar == null) { continue; } - NavigationBarView view = (NavigationBarView) mNavigationBars.get(i).getView(); + NavigationBarView view = (NavigationBarView) navBar.getView(); if (view != null) { view.updateStates(); } @@ -399,7 +399,7 @@ public class NavigationBarController implements Callbacks, if (i > 0) { pw.println(); } - mNavigationBars.get(i).dump(pw); + mNavigationBars.valueAt(i).dump(pw); } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 19e32783f765..35d5ca949f85 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -279,9 +279,11 @@ public class NavigationBarView extends FrameLayout implements return; } + // When in gestural and the IME is showing, don't use the nearest region since it will take + // gesture space away from the IME info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set(getButtonLocations(false /* includeFloatingRotationButton */, - false /* inScreen */)); + false /* inScreen */, false /* useNearestRegion */)); }; private final Consumer<Boolean> mRotationButtonListener = (visible) -> { @@ -981,7 +983,8 @@ public class NavigationBarView extends FrameLayout implements */ public void notifyActiveTouchRegions() { mOverviewProxyService.onActiveNavBarRegionChanges( - getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */)); + getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */, + true /* useNearestRegion */)); } private void updateButtonTouchRegionCache() { @@ -992,35 +995,49 @@ public class NavigationBarView extends FrameLayout implements .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions(); } + /** + * @param includeFloatingRotationButton Whether to include the floating rotation button in the + * region for all the buttons + * @param inScreenSpace Whether to return values in screen space or window space + * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds + * @return + */ private Region getButtonLocations(boolean includeFloatingRotationButton, - boolean inScreenSpace) { + boolean inScreenSpace, boolean useNearestRegion) { + if (useNearestRegion && !inScreenSpace) { + // We currently don't support getting the nearest region in anything but screen space + useNearestRegion = false; + } mTmpRegion.setEmpty(); updateButtonTouchRegionCache(); - updateButtonLocation(getBackButton(), inScreenSpace); - updateButtonLocation(getHomeButton(), inScreenSpace); - updateButtonLocation(getRecentsButton(), inScreenSpace); - updateButtonLocation(getImeSwitchButton(), inScreenSpace); - updateButtonLocation(getAccessibilityButton(), inScreenSpace); + updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion); if (includeFloatingRotationButton && mFloatingRotationButton.isVisible()) { + // Note: this button is floating so the nearest region doesn't apply updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace); } else { - updateButtonLocation(getRotateSuggestionButton(), inScreenSpace); + updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion); } if (mNavBarOverlayController.isNavigationBarOverlayEnabled() && mNavBarOverlayController.isVisible()) { + // Note: this button is floating so the nearest region doesn't apply updateButtonLocation(mNavBarOverlayController.getCurrentView(), inScreenSpace); } return mTmpRegion; } - private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace) { + private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace, + boolean useNearestRegion) { View view = button.getCurrentView(); if (view == null || !button.isVisible()) { return; } // If the button is tappable from perspective of NearestTouchFrame, then we'll // include the regions where the tap is valid instead of just the button layout location - if (mButtonFullTouchableRegions.containsKey(view)) { + if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) { mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION); return; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java index 870e3bed2b7a..422ffd524aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java @@ -200,7 +200,7 @@ public class NavigationModeController implements Dumpable { Log.d(TAG, " assetPaths="); ApkAssets[] assets = context.getResources().getAssets().getApkAssets(); for (ApkAssets a : assets) { - Log.d(TAG, " " + a.getAssetPath()); + Log.d(TAG, " " + a.getDebugName()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 2ea8657f88e5..a69ec278be91 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -23,7 +23,6 @@ import android.app.Activity; import android.app.INotificationManager; import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; -import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; @@ -34,12 +33,10 @@ import android.provider.Settings; import android.util.Log; import android.view.ViewGroup; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.R; +import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import java.util.Collections; import java.util.List; import javax.inject.Inject; @@ -54,15 +51,14 @@ public class PeopleSpaceActivity extends Activity { private ViewGroup mPeopleSpaceLayout; private IPeopleManager mPeopleManager; + private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; private INotificationManager mNotificationManager; private PackageManager mPackageManager; private LauncherApps mLauncherApps; private Context mContext; - private AppWidgetManager mAppWidgetManager; private NotificationEntryManager mNotificationEntryManager; private int mAppWidgetId; private boolean mShowSingleConversation; - private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); @Inject public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager) { @@ -81,8 +77,8 @@ public class PeopleSpaceActivity extends Activity { mPackageManager = getPackageManager(); mPeopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); + mPeopleSpaceWidgetManager = new PeopleSpaceWidgetManager(mContext); mLauncherApps = mContext.getSystemService(LauncherApps.class); - mAppWidgetManager = AppWidgetManager.getInstance(mContext); setTileViewsWithPriorityConversations(); mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); @@ -142,29 +138,13 @@ public class PeopleSpaceActivity extends Activity { + mAppWidgetId); } } - - PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId); - int[] widgetIds = new int[mAppWidgetId]; - // TODO: Populate new widget with existing conversation notification, if there is any. - PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, - mPeopleManager); - if (mLauncherApps != null) { - try { - 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); - } - } + mPeopleSpaceWidgetManager.addNewWidget(tile, mAppWidgetId); finishActivity(); } /** Finish activity with a successful widget configuration result. */ private void finishActivity() { if (DEBUG) Log.d(TAG, "Widget added!"); - mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED); setActivityResult(RESULT_OK); finish(); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 41080afb3604..f9a16c1c1970 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -220,7 +220,7 @@ public class PeopleSpaceUtils { } @Nullable - private static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, + public static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, AppWidgetManager appWidgetManager, Context context, int appWidgetId) { try { @@ -230,7 +230,7 @@ public class PeopleSpaceUtils { String pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); int userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); String shortcutId = widgetSp.getString(SHORTCUT_ID, EMPTY_STRING); - if (pkg.isEmpty() || shortcutId.isEmpty() || userId == INVALID_WIDGET_ID) { + if (!validKey(shortcutId, pkg, userId)) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); shortcutId = sp.getString(String.valueOf(appWidgetId), null); if (shortcutId == null) { @@ -268,6 +268,17 @@ public class PeopleSpaceUtils { } } + /** Returns stored widgets for the conversation specified. */ + public static Set<String> getStoredWidgetIds(SharedPreferences sp, String shortcutId, + String packageName, int userId) { + if (shortcutId == null || packageName == null) { + return new HashSet<>(); + } + String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); + return new HashSet<>(sp.getStringSet(key, new HashSet<>())); + } + + /** Best-effort attempts to migrate existing users to the new storage format. */ // TODO: Remove after sufficient time. Temporary migration storage for existing users. private static void migrateExistingUsersToNewStorage(Context context, String shortcutId, @@ -286,7 +297,11 @@ public class PeopleSpaceUtils { if (DEBUG) Log.d(TAG, "Migrate storage for " + entry.get().getUserName()); setStorageForTile(context, entry.get(), appWidgetId); } else { - Log.e(TAG, "Could not migrate user"); + Log.e(TAG, "Could not migrate user. Delete old storage"); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = sp.edit(); + editor.remove(String.valueOf(appWidgetId)); + editor.apply(); } } catch (Exception e) { Log.e(TAG, "Could not query conversations"); @@ -320,17 +335,10 @@ public class PeopleSpaceUtils { } /** Removes stored data when tile is deleted. */ - public static void removeStorageForTile(Context context, int widgetId) { - SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), - Context.MODE_PRIVATE); - String packageName = widgetSp.getString(PACKAGE_NAME, null); - String shortcutId = widgetSp.getString(SHORTCUT_ID, null); - int userId = widgetSp.getInt(USER_ID, -1); - + public static void removeStorageForTile(Context context, String key, int widgetId) { // Delete widgetId mapping to key. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); - String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); storedWidgetIds.remove(String.valueOf(widgetId)); editor.putStringSet(key, storedWidgetIds); @@ -338,6 +346,8 @@ public class PeopleSpaceUtils { editor.apply(); // Delete all data specifically mapped to widgetId. + SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), + Context.MODE_PRIVATE); SharedPreferences.Editor widgetEditor = widgetSp.edit(); widgetEditor.remove(PACKAGE_NAME); widgetEditor.remove(USER_ID); @@ -345,21 +355,6 @@ public class PeopleSpaceUtils { widgetEditor.apply(); } - /** - * Returns whether the data mapped to app widget specified by {@code appWidgetId} matches the - * requested update data. - */ - public static boolean isCorrectAppWidget(Context context, int appWidgetId, String shortcutId, - String packageName, int userId) { - SharedPreferences sp = context.getSharedPreferences(String.valueOf(appWidgetId), - Context.MODE_PRIVATE); - String storedPackage = sp.getString(PACKAGE_NAME, EMPTY_STRING); - int storedUserId = sp.getInt(USER_ID, INVALID_USER_ID); - String storedShortcutId = sp.getString(SHORTCUT_ID, EMPTY_STRING); - return storedPackage.equals(packageName) && storedShortcutId.equals(shortcutId) - && storedUserId == userId; - } - static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(Context context, List<PeopleSpaceTile> tiles, NotificationEntryManager notificationEntryManager) { if (notificationEntryManager == null) { @@ -396,39 +391,8 @@ public class PeopleSpaceUtils { return augmentTileFromNotification(context, tile, visibleNotifications.get(key).getSbn()); } - /** - * If incoming notification changed tile, store the changes in the tile options. - */ - public static void updateWidgetWithNotificationChanged(IPeopleManager peopleManager, - Context context, - StatusBarNotification sbn, - NotificationAction notificationAction, AppWidgetManager appWidgetManager, - int appWidgetId) { - PeopleSpaceTile storedTile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, - appWidgetId); - if (storedTile == null) { - if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); - return; - } - if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { - if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); - storedTile = augmentTileFromNotification(context, storedTile, sbn); - } else { - if (DEBUG) { - Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); - } - storedTile = storedTile - .toBuilder() - .setNotificationKey(null) - .setNotificationContent(null) - .setNotificationDataUri(null) - .setNotificationCategory(null) - .build(); - } - updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile); - } - - static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile, + /** Augments {@code tile} with the notification content from {@code sbn}. */ + public static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile, StatusBarNotification sbn) { Notification notification = sbn.getNotification(); if (notification == null) { @@ -992,7 +956,7 @@ public class PeopleSpaceUtils { } /** Update app widget options and the current view. */ - private static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager, + public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager, Context context, int appWidgetId, PeopleSpaceTile tile) { updateAppWidgetOptions(appWidgetManager, appWidgetId, tile); RemoteViews views = createRemoteViews(context, tile, appWidgetId); @@ -1065,10 +1029,19 @@ public class PeopleSpaceUtils { * <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li> * </ul> */ + @Nullable public static String getKey(String shortcutId, String packageName, int userId) { + if (!validKey(shortcutId, packageName, userId)) { + return null; + } return shortcutId + "/" + userId + "/" + packageName; } + /** Returns whether the key is valid. */ + public static boolean validKey(String shortcutId, String packageName, int userId) { + return !TextUtils.isEmpty(shortcutId) && !TextUtils.isEmpty(packageName) && userId >= 0; + } + /** 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/widget/PeopleSpaceWidgetEnabler.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java deleted file mode 100644 index 3df264421d75..000000000000 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java +++ /dev/null @@ -1,68 +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.systemui.people.widget; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.util.Log; - -import com.android.systemui.SystemUI; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.people.PeopleSpaceActivity; -import com.android.systemui.statusbar.FeatureFlags; - -import javax.inject.Inject; - -/** - * Enables People Space widgets. - */ -@SysUISingleton -public class PeopleSpaceWidgetEnabler extends SystemUI { - private static final String TAG = "PeopleSpaceWdgtEnabler"; - private Context mContext; - private FeatureFlags mFeatureFlags; - - @Inject - public PeopleSpaceWidgetEnabler(Context context, FeatureFlags featureFlags) { - super(context); - mContext = context; - mFeatureFlags = featureFlags; - } - - @Override - public void start() { - Log.d(TAG, "Starting service"); - try { - boolean showPeopleSpace = mFeatureFlags.isPeopleTileEnabled(); - mContext.getPackageManager().setComponentEnabledSetting( - new ComponentName(mContext, PeopleSpaceWidgetProvider.class), - showPeopleSpace - ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - mContext.getPackageManager().setComponentEnabledSetting( - new ComponentName(mContext, PeopleSpaceActivity.class), - showPeopleSpace - ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } catch (Exception e) { - Log.w(TAG, "Error enabling People Space widget:", e); - } - } -} 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 9e5c786b9a63..22ee9e89d0a0 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -16,12 +16,28 @@ package com.android.systemui.people.widget; +import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID; +import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; +import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID; +import static com.android.systemui.people.PeopleSpaceUtils.USER_ID; +import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotification; +import static com.android.systemui.people.PeopleSpaceUtils.getPeopleSpaceTile; +import static com.android.systemui.people.PeopleSpaceUtils.getStoredWidgetIds; +import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView; + import android.app.NotificationChannel; +import android.app.Person; +import android.app.people.ConversationChannel; import android.app.people.IPeopleManager; +import android.app.people.PeopleManager; +import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.net.Uri; import android.os.ServiceManager; import android.os.UserHandle; import android.preference.PreferenceManager; @@ -30,14 +46,18 @@ import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.appwidget.IAppWidgetService; -import com.android.systemui.R; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import javax.inject.Inject; @@ -49,52 +69,49 @@ public class PeopleSpaceWidgetManager { private static final String TAG = "PeopleSpaceWidgetMgr"; private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; + private final Object mLock = new Object(); private final Context mContext; - private IAppWidgetService mAppWidgetService; + private LauncherApps mLauncherApps; private AppWidgetManager mAppWidgetManager; - private IPeopleManager mPeopleManager; + private IPeopleManager mIPeopleManager; + private SharedPreferences mSharedPrefs; + private PeopleManager mPeopleManager; + public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + @GuardedBy("mLock") + public static Map<String, PeopleSpaceWidgetProvider.TileConversationListener> mListeners = + new HashMap<>(); @Inject - public PeopleSpaceWidgetManager(Context context, IAppWidgetService appWidgetService) { + public PeopleSpaceWidgetManager(Context context) { if (DEBUG) Log.d(TAG, "constructor"); mContext = context; - mAppWidgetService = appWidgetService; mAppWidgetManager = AppWidgetManager.getInstance(context); - mPeopleManager = IPeopleManager.Stub.asInterface( + mIPeopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); - } - - /** - * Constructor used for testing. - */ - @VisibleForTesting - protected PeopleSpaceWidgetManager(Context context) { - if (DEBUG) Log.d(TAG, "constructor"); - mContext = context; - mAppWidgetService = IAppWidgetService.Stub.asInterface( - ServiceManager.getService(Context.APPWIDGET_SERVICE)); + mLauncherApps = context.getSystemService(LauncherApps.class); + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + mPeopleManager = mContext.getSystemService(PeopleManager.class); } /** * AppWidgetManager setter used for testing. */ @VisibleForTesting - protected void setAppWidgetManager(IAppWidgetService appWidgetService, - AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { - mAppWidgetService = appWidgetService; + protected void setAppWidgetManager( + AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager, + PeopleManager peopleManager, LauncherApps launcherApps) { mAppWidgetManager = appWidgetManager; + mIPeopleManager = iPeopleManager; mPeopleManager = peopleManager; + mLauncherApps = launcherApps; } /** * Updates People Space widgets. */ - public void updateWidgets() { + public void updateWidgets(int[] widgetIds) { try { if (DEBUG) Log.d(TAG, "updateWidgets called"); - int[] widgetIds = mAppWidgetService.getAppWidgetIds( - new ComponentName(mContext, PeopleSpaceWidgetProvider.class) - ); if (widgetIds.length == 0) { if (DEBUG) Log.d(TAG, "no widgets to update"); return; @@ -103,14 +120,11 @@ public class PeopleSpaceWidgetManager { if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets"); boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; - if (showSingleConversation) { - PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, - mAppWidgetManager, mPeopleManager); - } else { - mAppWidgetService - .notifyAppWidgetViewDataChanged(mContext.getOpPackageName(), widgetIds, - R.id.widget_list_view); + synchronized (mLock) { + PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, + mAppWidgetManager, mIPeopleManager); + } } } catch (Exception e) { Log.e(TAG, "Exception: " + e); @@ -121,9 +135,9 @@ public class PeopleSpaceWidgetManager { * Check if any existing People tiles match the incoming notification change, and store the * change in the tile if so. */ - public void updateWidgetWithNotificationChanged(StatusBarNotification sbn, + public void updateWidgetsWithNotificationChanged(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction) { - if (DEBUG) Log.d(TAG, "updateWidgetWithNotificationChanged called"); + if (DEBUG) Log.d(TAG, "updateWidgetsWithNotificationChanged called"); boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (!showSingleConversation) { @@ -135,26 +149,22 @@ public class PeopleSpaceWidgetManager { if (DEBUG) Log.d(TAG, "Sbn shortcut id is null"); return; } - int[] widgetIds = mAppWidgetService.getAppWidgetIds( + int[] widgetIds = mAppWidgetManager.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class) ); if (widgetIds.length == 0) { Log.d(TAG, "No app widget ids returned"); return; } - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - int userId = UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(); - String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId); - Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); - if (storedWidgetIds.isEmpty()) { - Log.d(TAG, "No stored widget ids"); - return; - } - for (String widgetIdString : storedWidgetIds) { - int widgetId = Integer.parseInt(widgetIdString); - if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); - PeopleSpaceUtils.updateWidgetWithNotificationChanged(mPeopleManager, mContext, - sbn, notificationAction, mAppWidgetManager, widgetId); + synchronized (mLock) { + Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, sbnShortcutId, + sbn.getPackageName(), + UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier()); + for (String widgetIdString : storedWidgetIds) { + int widgetId = Integer.parseInt(widgetIdString); + if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); + updateStorageAndViewWithNotificationData(sbn, notificationAction, widgetId); + } } } catch (Exception e) { Log.e(TAG, "Exception: " + e); @@ -162,6 +172,91 @@ public class PeopleSpaceWidgetManager { } /** + * Update the tiles associated with the incoming conversation update. + */ + public void updateWidgetsWithConversationChanged(ConversationChannel conversation) { + ShortcutInfo info = conversation.getShortcutInfo(); + synchronized (mLock) { + Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, info.getId(), + info.getPackage(), + info.getUserId()); + for (String widgetIdString : storedWidgetIds) { + if (DEBUG) { + Log.d(TAG, + "Conversation update for widget " + widgetIdString + " , " + + info.getLabel()); + } + updateStorageAndViewWithConversationData(conversation, + Integer.valueOf(widgetIdString)); + } + } + } + + /** + * Update {@code appWidgetId} with the new data provided by {@code conversation}. + */ + private void updateStorageAndViewWithConversationData(ConversationChannel conversation, + int appWidgetId) { + PeopleSpaceTile storedTile = getPeopleSpaceTile(mIPeopleManager, mAppWidgetManager, + mContext, + appWidgetId); + if (storedTile == null) { + if (DEBUG) Log.d(TAG, "Could not find stored tile to add conversation to"); + return; + } + ShortcutInfo info = conversation.getShortcutInfo(); + Uri uri = null; + if (info.getPersons() != null && info.getPersons().length > 0) { + Person person = info.getPersons()[0]; + uri = person.getUri() == null ? null : Uri.parse(person.getUri()); + } + storedTile = storedTile.toBuilder() + .setUserName(info.getLabel()) + .setUserIcon( + PeopleSpaceTile.convertDrawableToIcon(mLauncherApps.getShortcutIconDrawable( + info, 0)) + ) + .setContactUri(uri) + .setStatuses(conversation.getStatuses()) + .setLastInteractionTimestamp(conversation.getLastEventTimestamp()) + .setIsImportantConversation(conversation.getParentNotificationChannel() != null + && conversation.getParentNotificationChannel().isImportantConversation()) + .build(); + updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId, storedTile); + } + + /** + * Update {@code appWidgetId} with the new data provided by {@code sbn}. + */ + private void updateStorageAndViewWithNotificationData( + StatusBarNotification sbn, + PeopleSpaceUtils.NotificationAction notificationAction, + int appWidgetId) { + PeopleSpaceTile storedTile = getPeopleSpaceTile(mIPeopleManager, mAppWidgetManager, + mContext, + appWidgetId); + if (storedTile == null) { + if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); + return; + } + if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { + if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); + storedTile = augmentTileFromNotification(mContext, storedTile, sbn); + } else { + if (DEBUG) { + Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); + } + storedTile = storedTile + .toBuilder() + .setNotificationKey(null) + .setNotificationContent(null) + .setNotificationDataUri(null) + .build(); + } + updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId, storedTile); + } + + /** * Attaches the manager to the pipeline, making it ready to receive events. Should only be * called once. */ @@ -174,8 +269,7 @@ public class PeopleSpaceWidgetManager { @Override public void onNotificationPosted( StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) { - if (DEBUG) Log.d(TAG, "onNotificationPosted"); - updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED); + updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED); } @Override @@ -183,8 +277,7 @@ public class PeopleSpaceWidgetManager { StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap ) { - if (DEBUG) Log.d(TAG, "onNotificationRemoved"); - updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); + updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override @@ -192,8 +285,7 @@ public class PeopleSpaceWidgetManager { StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap, int reason) { - if (DEBUG) Log.d(TAG, "onNotificationRemoved with reason " + reason); - updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); + updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override @@ -204,7 +296,6 @@ public class PeopleSpaceWidgetManager { @Override public void onNotificationsInitialized() { if (DEBUG) Log.d(TAG, "onNotificationsInitialized"); - updateWidgets(); } @Override @@ -213,11 +304,131 @@ public class PeopleSpaceWidgetManager { UserHandle user, NotificationChannel channel, int modificationType) { - if (DEBUG) Log.d(TAG, "onNotificationChannelModified"); if (channel.isConversation()) { - updateWidgets(); + updateWidgets(mAppWidgetManager.getAppWidgetIds( + new ComponentName(mContext, PeopleSpaceWidgetProvider.class) + )); } } }; -}
\ No newline at end of file + /** Adds {@code tile} mapped to {@code appWidgetId}. */ + public void addNewWidget(PeopleSpaceTile tile, int appWidgetId) { + mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED); + synchronized (mLock) { + if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getUserName()); + PeopleSpaceUtils.setStorageForTile(mContext, tile, appWidgetId); + } + try { + 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); + } + PeopleSpaceWidgetProvider provider = new PeopleSpaceWidgetProvider(); + provider.onUpdate(mContext, mAppWidgetManager, new int[]{appWidgetId}); + } + + /** Registers a conversation listener for {@code appWidgetId} if not already registered. */ + public void registerConversationListenerIfNeeded(int widgetId, + PeopleSpaceWidgetProvider.TileConversationListener newListener) { + // Retrieve storage needed for registration. + String packageName; + String shortcutId; + int userId; + String key; + synchronized (mLock) { + SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId), + Context.MODE_PRIVATE); + packageName = widgetSp.getString(PACKAGE_NAME, null); + shortcutId = widgetSp.getString(SHORTCUT_ID, null); + userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); + key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); + if (key == null) { + if (DEBUG) Log.e(TAG, "Could not register " + widgetId); + return; + } + } + synchronized (mListeners) { + if (mListeners.containsKey(key)) { + if (DEBUG) Log.d(TAG, "Already registered listener"); + return; + } + if (DEBUG) Log.d(TAG, "Register listener for " + widgetId + " with " + key); + mListeners.put(key, newListener); + } + mPeopleManager.registerConversationListener(packageName, + userId, + shortcutId, newListener, + mContext.getMainExecutor()); + } + + /** Deletes all storage, listeners, and caching for {@code appWidgetIds}. */ + public void deleteWidgets(int[] appWidgetIds) { + for (int widgetId : appWidgetIds) { + if (DEBUG) Log.d(TAG, "Widget removed: " + widgetId); + mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); + // Retrieve storage needed for widget deletion. + String packageName; + String shortcutId; + int userId; + String key; + Set<String> storedWidgetIdsForKey; + synchronized (mLock) { + SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId), + Context.MODE_PRIVATE); + packageName = widgetSp.getString(PACKAGE_NAME, null); + shortcutId = widgetSp.getString(SHORTCUT_ID, null); + userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); + key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); + if (key == null) { + if (DEBUG) Log.e(TAG, "Could not delete " + widgetId); + return; + } + storedWidgetIdsForKey = new HashSet<>( + mSharedPrefs.getStringSet(key, new HashSet<>())); + } + synchronized (mLock) { + PeopleSpaceUtils.removeStorageForTile(mContext, key, widgetId); + } + // If another tile with the conversation is still stored, we need to keep the listener. + if (DEBUG) Log.d(TAG, "Stored widget IDs: " + storedWidgetIdsForKey.toString()); + if (storedWidgetIdsForKey.contains(String.valueOf(widgetId)) + && storedWidgetIdsForKey.size() == 1) { + if (DEBUG) Log.d(TAG, "Remove caching and listener"); + unregisterConversationListener(key, widgetId); + uncacheConversationShortcut(shortcutId, packageName, userId); + } + } + } + + /** Unregisters the conversation listener for {@code appWidgetId}. */ + private void unregisterConversationListener(String key, int appWidgetId) { + PeopleSpaceWidgetProvider.TileConversationListener registeredListener; + synchronized (mListeners) { + registeredListener = mListeners.get(key); + if (registeredListener == null) { + if (DEBUG) Log.d(TAG, "Cannot find listener to unregister"); + return; + } + if (DEBUG) Log.d(TAG, "Unregister listener for " + appWidgetId + " with " + key); + mListeners.remove(key); + } + mPeopleManager.unregisterConversationListener(registeredListener); + } + + /** Uncaches the conversation shortcut. */ + private void uncacheConversationShortcut(String shortcutId, String packageName, int userId) { + try { + if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId); + mLauncherApps.uncacheShortcuts(packageName, + Collections.singletonList(shortcutId), + UserHandle.of(userId), + LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); + } catch (Exception e) { + Log.d(TAG, "Exception uncaching shortcut:" + e); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java index 90baf56e0137..c0c18471ba32 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java @@ -16,31 +16,17 @@ package com.android.systemui.people.widget; -import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; -import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID; -import static com.android.systemui.people.PeopleSpaceUtils.USER_ID; - -import android.app.PendingIntent; -import android.app.people.IPeopleManager; +import android.annotation.NonNull; +import android.app.people.ConversationChannel; +import android.app.people.PeopleManager; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.LauncherApps; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; -import android.widget.RemoteViews; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.UiEventLoggerImpl; -import com.android.systemui.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.people.PeopleSpaceUtils; -import java.util.Collections; - /** People Space Widget Provider class. */ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { private static final String TAG = "PeopleSpaceWidgetPvd"; @@ -50,7 +36,26 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; public static final String EXTRA_USER_HANDLE = "extra_user_handle"; - public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + public PeopleSpaceWidgetManager peopleSpaceWidgetManager; + + /** Listener for the shortcut data changes. */ + public class TileConversationListener implements PeopleManager.ConversationListener { + + @Override + public void onConversationUpdate(@NonNull ConversationChannel conversation) { + if (DEBUG) { + Log.d(TAG, + "Received updated conversation: " + + conversation.getShortcutInfo().getLabel()); + } + if (peopleSpaceWidgetManager == null) { + // This shouldn't happen since onUpdate is called at reboot. + Log.e(TAG, "Skipping conversation update: WidgetManager uninitialized"); + return; + } + peopleSpaceWidgetManager.updateWidgetsWithConversationChanged(conversation); + } + } /** Called when widget updates. */ @Override @@ -58,70 +63,32 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { super.onUpdate(context, appWidgetManager, appWidgetIds); if (DEBUG) Log.d(TAG, "onUpdate called"); - boolean showSingleConversation = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; - if (showSingleConversation) { - PeopleSpaceUtils.updateSingleConversationWidgets(context, appWidgetIds, - appWidgetManager, IPeopleManager.Stub.asInterface( - ServiceManager.getService(Context.PEOPLE_SERVICE))); - return; - } - // Perform this loop procedure for each App Widget that belongs to this provider + ensurePeopleSpaceWidgetManagerInitialized(context); + peopleSpaceWidgetManager.updateWidgets(appWidgetIds); for (int appWidgetId : appWidgetIds) { - RemoteViews views = - new RemoteViews(context.getPackageName(), R.layout.people_space_widget); - - Intent intent = new Intent(context, PeopleSpaceWidgetService.class); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - views.setRemoteAdapter(R.id.widget_list_view, intent); - - Intent activityIntent = new Intent(context, LaunchConversationActivity.class); - activityIntent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_NO_HISTORY - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - PendingIntent pendingIntent = PendingIntent.getActivity( - context, - appWidgetId, - activityIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent); + PeopleSpaceWidgetProvider.TileConversationListener + newListener = new PeopleSpaceWidgetProvider.TileConversationListener(); + peopleSpaceWidgetManager.registerConversationListenerIfNeeded(appWidgetId, + newListener); + } + return; + } - // Tell the AppWidgetManager to perform an update on the current app widget - appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list_view); - appWidgetManager.updateAppWidget(appWidgetId, views); + private void ensurePeopleSpaceWidgetManagerInitialized(Context context) { + if (peopleSpaceWidgetManager == null) { + peopleSpaceWidgetManager = new PeopleSpaceWidgetManager(context); } } @Override public void onDeleted(Context context, int[] appWidgetIds) { super.onDeleted(context, appWidgetIds); - LauncherApps launcherApps = context.getSystemService(LauncherApps.class); - - for (int widgetId : appWidgetIds) { - if (DEBUG) Log.d(TAG, "Widget removed"); - mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); - if (launcherApps != null) { - SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), - Context.MODE_PRIVATE); - String packageName = widgetSp.getString(PACKAGE_NAME, null); - String shortcutId = widgetSp.getString(SHORTCUT_ID, null); - int userId = widgetSp.getInt(USER_ID, -1); + ensurePeopleSpaceWidgetManagerInitialized(context); + peopleSpaceWidgetManager.deleteWidgets(appWidgetIds); + } - if (packageName != null && shortcutId != null && userId != -1) { - try { - if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId); - launcherApps.uncacheShortcuts(packageName, - Collections.singletonList(shortcutId), - UserHandle.of(userId), - LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); - } catch (Exception e) { - Log.d(TAG, "Exception uncaching shortcut:" + e); - } - } - } - PeopleSpaceUtils.removeStorageForTile(context, widgetId); - } + @VisibleForTesting + public void setPeopleSpaceWidgetManager(PeopleSpaceWidgetManager manager) { + peopleSpaceWidgetManager = manager; } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt index 680a617e4d26..8ec9b682ffdc 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt @@ -47,7 +47,7 @@ class PrivacyDialog( context: Context, private val list: List<PrivacyElement>, activityStarter: (String, Int) -> Unit -) : SystemUIDialog(context, R.style.ScreenRecord) { +) : SystemUIDialog(context, R.style.PrivacyDialog) { private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>() private val dismissed = AtomicBoolean(false) @@ -64,6 +64,7 @@ class PrivacyDialog( super.onCreate(savedInstanceState) window?.apply { attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() + attributes.receiveInsetsIgnoringZOrder = true setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT) setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java new file mode 100644 index 000000000000..42d603ec5051 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs; + +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.display.ColorDisplayManager; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.provider.Settings; + +import androidx.annotation.NonNull; + +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.CallbackController; +import com.android.systemui.util.settings.SecureSettings; + +import java.util.ArrayList; + +import javax.inject.Inject; + +/** + * @hide + */ +public class ReduceBrightColorsController implements + CallbackController<ReduceBrightColorsController.Listener> { + private final ColorDisplayManager mManager; + private final UserTracker mUserTracker; + private UserTracker.Callback mCurrentUserTrackerCallback; + private final Handler mHandler; + private final ContentObserver mContentObserver; + private final SecureSettings mSecureSettings; + private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>(); + + @Inject + public ReduceBrightColorsController(UserTracker userTracker, + @Background Handler handler, + ColorDisplayManager colorDisplayManager, + SecureSettings secureSettings) { + mManager = colorDisplayManager; + mUserTracker = userTracker; + mHandler = handler; + mSecureSettings = secureSettings; + mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + final String setting = uri == null ? null : uri.getLastPathSegment(); + synchronized (mListeners) { + if (setting != null && mListeners.size() != 0) { + if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) { + for (Listener listener : mListeners) { + listener.onActivated(mManager.isReduceBrightColorsActivated()); + } + } + } + } + } + }; + + mCurrentUserTrackerCallback = new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, Context userContext) { + synchronized (mListeners) { + if (mListeners.size() > 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + mSecureSettings.registerContentObserverForUser( + Settings.Secure.getUriFor( + Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED), + false, mContentObserver, newUser); + } + } + } + }; + mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler)); + } + + @Override + public void addCallback(@NonNull Listener listener) { + synchronized (mListeners) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + if (mListeners.size() == 1) { + mSecureSettings.registerContentObserverForUser( + Settings.Secure.getUriFor( + Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED), + false, mContentObserver, mUserTracker.getUserId()); + } + } + } + } + + @Override + public void removeCallback(@androidx.annotation.NonNull Listener listener) { + synchronized (mListeners) { + if (mListeners.remove(listener) && mListeners.size() == 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + } + } + } + + /** Returns {@code true} if Reduce Bright Colors is activated */ + public boolean isReduceBrightColorsActivated() { + return mManager.isReduceBrightColorsActivated(); + } + + /** Sets the activation state of Reduce Bright Colors */ + public void setReduceBrightColorsActivated(boolean activated) { + mManager.setReduceBrightColorsActivated(activated); + } + + /** + * Listener invoked whenever the Reduce Bright Colors settings are changed. + */ + public interface Listener { + /** + * Listener invoked when the activated state changes. + * + * @param activated {@code true} if Reduce Bright Colors is activated. + */ + default void onActivated(boolean activated) { + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index c3cc3af10e83..52f111e7ab48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs import android.content.Context import android.util.AttributeSet -import com.android.systemui.R open class SideLabelTileLayout( context: Context, @@ -28,9 +27,6 @@ open class SideLabelTileLayout( override fun updateResources(): Boolean { return super.updateResources().also { mMaxAllowedRows = 4 - mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt() - mCellMarginVertical = mCellMarginHorizontal - mMaxCellHeight = context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java index 47cb45b14b9b..ce8f6c1737d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java @@ -16,19 +16,19 @@ package com.android.systemui.qs.customize; import android.content.Context; import android.view.View; -import android.widget.TextView; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.tileimpl.QSTileView; -public class CustomizeTileView extends QSTileView { +public class CustomizeTileView extends QSTileView implements TileAdapter.CustomizeView { private boolean mShowAppLabel; public CustomizeTileView(Context context, QSIconView icon) { super(context, icon); } + @Override public void setShowAppLabel(boolean showAppLabel) { mShowAppLabel = showAppLabel; mSecondLine.setVisibility(showAppLabel ? View.VISIBLE : View.GONE); @@ -41,10 +41,6 @@ public class CustomizeTileView extends QSTileView { mSecondLine.setVisibility(mShowAppLabel ? View.VISIBLE : View.GONE); } - public TextView getAppLabel() { - return mSecondLine; - } - @Override protected boolean animationsEnabled() { return false; @@ -54,4 +50,9 @@ public class CustomizeTileView extends QSTileView { public boolean isLongClickable() { return false; } + + @Override + public void changeState(QSTile.State state) { + handleStateChanged(state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt new file mode 100644 index 000000000000..4ffcd8cdd9ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt @@ -0,0 +1,50 @@ +package com.android.systemui.qs.customize + +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.QSTileViewHorizontal + +/** + * Class for displaying tiles in [QSCustomizer] with the new design (labels on the side). + * + * This is a class parallel to [CustomizeTileView], but inheriting from [QSTileViewHorizontal]. + */ +class CustomizeTileViewHorizontal( + context: Context, + icon: QSIconView +) : QSTileViewHorizontal(context, icon), + TileAdapter.CustomizeView { + + private var showAppLabel = false + + override fun setShowAppLabel(showAppLabel: Boolean) { + this.showAppLabel = showAppLabel + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + mLabel.isSingleLine = showAppLabel + } + + override fun handleStateChanged(state: QSTile.State) { + super.handleStateChanged(state) + mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE + } + + override fun animationsEnabled(): Boolean { + return false + } + + override fun isLongClickable(): Boolean { + return false + } + + override fun changeState(state: QSTile.State) { + handleStateChanged(state) + } + + override fun newTileBackground(): Drawable? { + super.newTileBackground() + return paintDrawable + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 7a91421b00a1..0adc8448b89f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -14,6 +14,8 @@ package com.android.systemui.qs.customize; +import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; + import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -30,6 +32,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; @@ -41,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.customize.TileAdapter.Holder; @@ -49,11 +53,13 @@ import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; +import com.android.systemui.qs.tileimpl.QSTileView; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; /** */ @QSScope @@ -75,7 +81,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private static final int ACTION_ADD = 1; private static final int ACTION_MOVE = 2; - private static final int NUM_COLUMNS_ID = R.integer.quick_settings_edit_num_columns; + private static final int NUM_COLUMNS_ID = R.integer.quick_settings_num_columns; private final Context mContext; @@ -102,9 +108,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private final AccessibilityDelegateCompat mAccessibilityDelegate; private RecyclerView mRecyclerView; private int mNumColumns; + private final boolean mUseHorizontalTiles; @Inject - public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) { + public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger, + @Named(QS_LABELS_FLAG) boolean useHorizontalTiles) { mContext = context; mHost = qsHost; mUiEventLogger = uiEventLogger; @@ -114,6 +122,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID); mAccessibilityDelegate = new TileAdapterDelegate(); + mUseHorizontalTiles = useHorizontalTiles; } @Override @@ -271,7 +280,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent, false); - frame.addView(new CustomizeTileView(context, new QSIconViewImpl(context))); + View view = mUseHorizontalTiles + ? new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context)) + : new CustomizeTileView(context, new QSIconViewImpl(context)); + frame.addView(view); return new Holder(frame); } @@ -354,8 +366,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } info.state.expandedAccessibilityClassName = ""; - holder.mTileView.handleStateChanged(info.state); - holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem); + // The holder has a tileView, therefore this call is not null + holder.getTileAsCustomizeView().changeState(info.state); + holder.getTileAsCustomizeView().setShowAppLabel(position > mEditIndex && !info.isSystem); holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); holder.mTileView.setClickable(true); holder.mTileView.setOnClickListener(null); @@ -534,25 +547,34 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } public class Holder extends ViewHolder { - private CustomizeTileView mTileView; + private QSTileView mTileView; public Holder(View itemView) { super(itemView); if (itemView instanceof FrameLayout) { - mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0); - mTileView.setBackground(null); + mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.setBackground(null); + } mTileView.getIcon().disableAnimation(); mTileView.setTag(this); ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate); } } + @Nullable + public CustomizeView getTileAsCustomizeView() { + return (CustomizeView) mTileView; + } + public void clearDrag() { itemView.clearAnimation(); - mTileView.findViewById(R.id.tile_label).clearAnimation(); - mTileView.findViewById(R.id.tile_label).setAlpha(1); - mTileView.getAppLabel().clearAnimation(); - mTileView.getAppLabel().setAlpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).clearAnimation(); + mTileView.findViewById(R.id.tile_label).setAlpha(1); + mTileView.getAppLabel().clearAnimation(); + mTileView.getAppLabel().setAlpha(.6f); + } } public void startDrag() { @@ -560,12 +582,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(DRAG_SCALE) .scaleY(DRAG_SCALE); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(0); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(0); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(0); + } } public void stopDrag() { @@ -573,12 +597,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(1) .scaleY(1); - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(1); - mTileView.getAppLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(.6f); + if (mTileView instanceof CustomizeTileView) { + mTileView.findViewById(R.id.tile_label).animate() + .setDuration(DRAG_LENGTH) + .alpha(1); + mTileView.getAppLabel().animate() + .setDuration(DRAG_LENGTH) + .alpha(.6f); + } } boolean canRemove() { @@ -722,7 +748,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta int position = mCurrentDrag.getAdapterPosition(); if (position == RecyclerView.NO_POSITION) return; TileInfo info = mTiles.get(position); - mCurrentDrag.mTileView.setShowAppLabel( + ((CustomizeView) mCurrentDrag.mTileView).setShowAppLabel( position > mEditIndex && !info.isSystem); mCurrentDrag.stopDrag(); mCurrentDrag = null; @@ -782,4 +808,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta public void onSwiped(ViewHolder viewHolder, int direction) { } }; + + interface CustomizeView { + void setShowAppLabel(boolean showAppLabel); + void changeState(@NonNull QSTile.State state); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 33713f3724c7..d41bd7ad4d3c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.dagger; +import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; + import android.content.Context; import android.hardware.display.NightDisplayListener; import android.os.Handler; @@ -25,6 +27,7 @@ import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.policy.CastController; @@ -32,6 +35,8 @@ import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.util.settings.SecureSettings; +import javax.inject.Named; + import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -54,7 +59,9 @@ public interface QSModule { DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, - CastController castController) { + CastController castController, + ReduceBrightColorsController reduceBrightColorsController, + @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { AutoTileManager manager = new AutoTileManager( context, autoAddTrackerBuilder, @@ -65,7 +72,9 @@ public interface QSModule { dataSaverController, managedProfileController, nightDisplayListener, - castController + castController, + reduceBrightColorsController, + isReduceBrightColorsAvailable ); manager.init(); return manager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 38e2ba4df79a..33ca7d6bafd8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -259,23 +259,30 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); final StringBuilder stateDescription = new StringBuilder(); + String text = ""; switch (state.state) { case Tile.STATE_UNAVAILABLE: - stateDescription.append(mContext.getString(R.string.tile_unavailable)); + text = mContext.getString(R.string.tile_unavailable); break; case Tile.STATE_INACTIVE: if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_off)); + text = mContext.getString(R.string.switch_bar_off); } break; case Tile.STATE_ACTIVE: if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_on)); + text = mContext.getString(R.string.switch_bar_on); } break; default: break; } + if (!TextUtils.isEmpty(text)) { + stateDescription.append(text); + if (TextUtils.isEmpty(state.secondaryLabel)) { + state.secondaryLabel = text; + } + } if (!TextUtils.isEmpty(state.stateDescription)) { stateDescription.append(", "); stateDescription.append(state.stateDescription); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index 207b25d001b9..b59326ae56d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -174,4 +174,8 @@ public class QSTileView extends QSTileBaseView { mLabelContainer.setClickable(false); mLabelContainer.setLongClickable(false); } + + public TextView getAppLabel() { + return mSecondLine; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index 07d48f32ff20..231037fdd158 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -35,13 +35,13 @@ import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState // Placeholder private const val CORNER_RADIUS = 40f -class QSTileViewHorizontal( +open class QSTileViewHorizontal( context: Context, icon: QSIconView ) : QSTileView(context, icon, false) { - private var paintDrawable: PaintDrawable? = null - private var paintColor = Color.TRANSPARENT + protected var paintDrawable: PaintDrawable? = null + private var paintColor = Color.WHITE private var paintAnimator: ValueAnimator? = null init { @@ -103,7 +103,7 @@ class QSTileViewHorizontal( mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null - val allowAnimations = animationsEnabled() && paintColor != Color.TRANSPARENT + val allowAnimations = animationsEnabled() && paintColor != Color.WHITE val newColor = getCircleColor(state.state) if (allowAnimations) { animateToNewState(newColor) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index f94cabcee297..aec7b9a4b6b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -33,46 +33,39 @@ 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.QSHost; -import com.android.systemui.qs.SecureSetting; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.util.settings.SecureSettings; import javax.inject.Inject; import javax.inject.Named; /** Quick settings tile: Reduce Bright Colors **/ -public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { +public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> + implements ReduceBrightColorsController.Listener{ //TODO(b/170973645): get icon drawable private final Icon mIcon = null; - private final SecureSetting mActivatedSetting; private final boolean mIsAvailable; + private final ReduceBrightColorsController mReduceBrightColorsController; + private boolean mIsListening; @Inject public ReduceBrightColorsTile( @Named(RBC_AVAILABLE) boolean isAvailable, + ReduceBrightColorsController reduceBrightColorsController, QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, - QSLogger qsLogger, - UserTracker userTracker, - SecureSettings secureSettings + QSLogger qsLogger ) { super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, activityStarter, qsLogger); - - mActivatedSetting = new SecureSetting(secureSettings, mainHandler, - Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, userTracker.getUserId()) { - @Override - protected void handleValueChanged(int value, boolean observedChange) { - refreshState(); - } - }; + mReduceBrightColorsController = reduceBrightColorsController; + mReduceBrightColorsController.observe(getLifecycle(), this); mIsAvailable = isAvailable; } @@ -84,7 +77,6 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { @Override protected void handleDestroy() { super.handleDestroy(); - mActivatedSetting.setListening(false); } @Override @@ -93,25 +85,13 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { } @Override - public void handleSetListening(boolean listening) { - super.handleSetListening(listening); - mActivatedSetting.setListening(listening); - } - - @Override - protected void handleUserSwitch(int newUserId) { - mActivatedSetting.setUserId(newUserId); - refreshState(); - } - - @Override public Intent getLongClickIntent() { return new Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS); } @Override protected void handleClick() { - mActivatedSetting.setValue(mState.value ? 0 : 1); + mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value); } @Override @@ -121,7 +101,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { @Override protected void handleUpdateState(BooleanState state, Object arg) { - state.value = mActivatedSetting.getValue() == 1; + state.value = mReduceBrightColorsController.isReduceBrightColorsActivated(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_reduce_bright_colors_label); state.expandedAccessibilityClassName = Switch.class.getName(); @@ -132,4 +112,9 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> { public int getMetricsCategory() { return 0; } + + @Override + public void onActivated(boolean activated) { + refreshState(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 5b2a7e7ff617..e7d42832878b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -662,14 +662,29 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void startIntent(PendingIntent intent, int stage, int position, Bundle options) { + 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, stage, position, options)); + 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); } @@ -789,10 +804,10 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void onTaskStageChanged(int taskId, int stage) { + public void onTaskStageChanged(int taskId, int stage, boolean visible) { try { if (mISplitScreenListener != null) { - mISplitScreenListener.onTaskStageChanged(taskId, stage); + mISplitScreenListener.onTaskStageChanged(taskId, stage, visible); } } catch (RemoteException e) { Log.e(TAG_OPS, "onTaskStageChanged", e); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 197582104f8e..bd46ffec759b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -351,7 +351,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_save_message)) + .setContentTitle(getResources().getString(R.string.screenrecord_save_title)) + .setContentText(getResources().getString(R.string.screenrecord_save_text)) .setContentIntent(PendingIntent.getActivity( this, REQUEST_CODE, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index 5b55864eed8a..c066619d049a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -65,6 +65,9 @@ public class CropView extends View { private float mTopDelta = 0f; private float mBottomDelta = 0f; + private int mExtraTopPadding; + private int mExtraBottomPadding; + private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE; private float mStartingY; // y coordinate of ACTION_DOWN private CropInteractionListener mCropInteractionListener; @@ -117,12 +120,13 @@ public class CropView extends View { if (mCurrentDraggingBoundary != CropBoundary.NONE) { float delta = event.getY() - mStartingY; if (mCurrentDraggingBoundary == CropBoundary.TOP) { - mTopDelta = pixelsToFraction((int) MathUtils.constrain(delta, -topPx, + mTopDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta, + -topPx + mExtraTopPadding, bottomPx - 2 * mCropTouchMargin - topPx)); } else { // Bottom - mBottomDelta = pixelsToFraction((int) MathUtils.constrain(delta, + mBottomDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta, topPx + 2 * mCropTouchMargin - bottomPx, - getHeight() - bottomPx)); + getHeight() - bottomPx - mExtraBottomPadding)); } updateListener(event); invalidate(); @@ -195,6 +199,16 @@ public class CropView extends View { } /** + * Set additional top and bottom padding for the image being cropped (used when the + * corresponding ImageView doesn't take the full height). + */ + public void setExtraPadding(int top, int bottom) { + mExtraTopPadding = top; + mExtraBottomPadding = bottom; + invalidate(); + } + + /** * @return value [0,1] representing the position of the top crop boundary. Does not reflect * changes from any in-progress touch input. */ @@ -244,12 +258,22 @@ public class CropView extends View { true, mHandlePaint); } + /** + * Convert the given fraction position to pixel position within the View. + */ private int fractionToPixels(float frac) { - return (int) (frac * getHeight()); + return (int) (mExtraTopPadding + frac * getImageHeight()); + } + + private int getImageHeight() { + return getHeight() - mExtraTopPadding - mExtraBottomPadding; } - private float pixelsToFraction(int px) { - return px / (float) getHeight(); + /** + * Convert the given pixel distance to fraction of the image. + */ + private float pixelDistanceToFraction(int px) { + return px / (float) getImageHeight(); } private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 5a13ea55222d..4dc846e0e95d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -157,6 +157,9 @@ public class LongScreenshotActivity extends Activity { }); } } + mPreview.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + updateCropLocation()); } @Override @@ -305,6 +308,27 @@ public class LongScreenshotActivity extends Activity { } } + private void updateCropLocation() { + Drawable drawable = mPreview.getDrawable(); + if (drawable == null) { + return; + } + + float imageRatio = drawable.getBounds().width() / (float) drawable.getBounds().height(); + float viewRatio = mPreview.getWidth() / (float) mPreview.getHeight(); + + if (imageRatio > viewRatio) { + // Image is full width and height is constrained, compute extra padding to inform + // CropView + float imageHeight = mPreview.getHeight() * viewRatio / imageRatio; + int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2; + mCropView.setExtraPadding(extraPadding, extraPadding); + } else { + // Image is full height + mCropView.setExtraPadding(0, 0); + } + } + private void doCapture() { mScrollCaptureController.start(mConnection, new ScrollCaptureController.ScrollCaptureCallback() { @@ -319,6 +343,7 @@ public class LongScreenshotActivity extends Activity { Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x " + imageTileSet.getHeight()); mPreview.setImageDrawable(imageTileSet.getDrawable()); + updateCropLocation(); mMagnifierView.setDrawable(imageTileSet.getDrawable(), imageTileSet.getWidth(), imageTileSet.getHeight()); mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java index 7a0ec4c520b2..90f304262ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java @@ -16,6 +16,8 @@ package com.android.systemui.screenshot; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; @@ -28,6 +30,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.ViewPropertyAnimator; import androidx.annotation.Nullable; @@ -35,7 +38,7 @@ import com.android.systemui.R; /** * MagnifierView shows a full-res cropped circular display of a given ImageTileSet, contents and - * positioning dereived from events from a CropView to which it listens. + * positioning derived from events from a CropView to which it listens. * * Not meant to be a general-purpose magnifier! */ @@ -57,6 +60,20 @@ public class MagnifierView extends View implements CropView.CropInteractionListe private float mLastCropPosition; private CropView.CropBoundary mCropBoundary; + private ViewPropertyAnimator mTranslationAnimator; + private final Animator.AnimatorListener mTranslationAnimatorListener = + new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + mTranslationAnimator = null; + } + + @Override + public void onAnimationEnd(Animator animation) { + mTranslationAnimator = null; + } + }; + public MagnifierView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } @@ -133,6 +150,8 @@ public class MagnifierView extends View implements CropView.CropInteractionListe public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary, float cropPosition, int cropPositionPx) { mCropBoundary = boundary; + boolean touchOnRight = event.getX() > getParentWidth() / 2; + float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastCropPosition = cropPosition; @@ -144,11 +163,22 @@ public class MagnifierView extends View implements CropView.CropInteractionListe setAlpha(0f); setTranslationX((getParentWidth() - getWidth()) / 2); setVisibility(View.VISIBLE); - boolean touchOnRight = event.getX() > getParentWidth() / 2; - float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); - animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f).start(); + mTranslationAnimator = + animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f); + mTranslationAnimator.setListener(mTranslationAnimatorListener); + mTranslationAnimator.start(); break; case MotionEvent.ACTION_MOVE: + // The touch is near the middle if it's within 10% of the center point. + // We don't want to animate horizontally if the touch is near the middle. + boolean nearMiddle = Math.abs(event.getX() - getParentWidth() / 2) + < getParentWidth() / 10f; + boolean viewOnLeft = getTranslationX() < (getParentWidth() - getWidth()) / 2; + if (!nearMiddle && viewOnLeft != touchOnRight && mTranslationAnimator == null) { + mTranslationAnimator = animate().translationX(translateXTarget); + mTranslationAnimator.setListener(mTranslationAnimatorListener); + mTranslationAnimator.start(); + } mLastCropPosition = cropPosition; setTranslationY(cropPositionPx - getHeight() / 2); invalidate(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 805ac7cf1ec9..3d6dea3cd3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -519,7 +519,7 @@ public class ScreenshotController { setWindowFocusable(true); if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) { + SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) { View decorView = mWindow.getDecorView(); // Wait until this window is attached to request because it is diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 6cdf6ab5154e..58a54f6ce0ed 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -82,7 +82,7 @@ public class ScreenshotNotificationsController { dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE); if (intent != null) { final PendingIntent pendingIntent = PendingIntent.getActivityAsUser( - mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED, null, UserHandle.CURRENT); + mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); b.setContentIntent(pendingIntent); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index bf65132166b6..9da6b8f240e9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -38,13 +38,15 @@ public class ScrollCaptureController { private static final float MAX_PAGES_DEFAULT = 3f; private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages"; + // Portion of the tiles to be acquired above the starting position in infinite scroll + // situations. 1.0 means maximize the area above, 0 means just go down. + private static final float IDEAL_PORTION_ABOVE = 0.4f; - private static final int UP = -1; - private static final int DOWN = 1; + private boolean mScrollingUp = true; + // If true, stop acquiring images when no more bitmap data is available in the current direction + // or if the desired bitmap size is reached. + private boolean mFinishOnBoundary; - private int mDirection = DOWN; - private boolean mAtBottomEdge; - private boolean mAtTopEdge; private Session mSession; public static final int MAX_HEIGHT = 12000; @@ -86,7 +88,8 @@ public class ScrollCaptureController { } private void onCaptureResult(CaptureResult result) { - Log.d(TAG, "onCaptureResult: " + result); + Log.d(TAG, "onCaptureResult: " + result + " scrolling up: " + mScrollingUp + + " finish on boundary: " + mFinishOnBoundary); boolean emptyResult = result.captured.height() == 0; boolean partialResult = !emptyResult && result.captured.height() < result.requested.height(); @@ -94,34 +97,28 @@ public class ScrollCaptureController { if (partialResult || emptyResult) { // Potentially reached a vertical boundary. Extend in the other direction. - switch (mDirection) { - case DOWN: - Log.d(TAG, "Reached bottom edge."); - mAtBottomEdge = true; - mDirection = UP; - break; - case UP: - Log.d(TAG, "Reached top edge."); - mAtTopEdge = true; - mDirection = DOWN; - break; + if (mFinishOnBoundary) { + finish = true; + } else { + // We hit a boundary, clear the tiles, capture everything in the opposite direction, + // then finish. + mImageTileSet.clear(); + mFinishOnBoundary = true; + mScrollingUp = !mScrollingUp; } - - if (mAtTopEdge && mAtBottomEdge) { - Log.d(TAG, "Reached both top and bottom edge, ending."); + } else { + // Got the full requested result, but may have got enough bitmap data now + int expectedTiles = mImageTileSet.size() + 1; + boolean hitMaxTiles = expectedTiles >= mSession.getMaxTiles(); + if (hitMaxTiles && mFinishOnBoundary) { finish = true; } else { - // only reverse if the edge was relatively close to the starting point - if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) { - Log.d(TAG, "Restarting in reverse direction."); - - // Because of temporary limitations, we cannot just jump to the opposite edge - // and continue there. Instead, clear the results and start over capturing from - // here in the other direction. - mImageTileSet.clear(); - } else { - Log.d(TAG, "Capture is tall enough, stopping here."); - finish = true; + if (mScrollingUp) { + if (expectedTiles >= mSession.getMaxTiles() * IDEAL_PORTION_ABOVE) { + // We got enough above the start point, now see how far down it can go. + mImageTileSet.clear(); + mScrollingUp = false; + } } } } @@ -136,9 +133,8 @@ public class ScrollCaptureController { // Stop when "too tall" - if (mImageTileSet.size() >= mSession.getMaxTiles() - || mImageTileSet.getHeight() > MAX_HEIGHT) { - Log.d(TAG, "Max height and/or tile count reached."); + if (mImageTileSet.getHeight() > MAX_HEIGHT) { + Log.d(TAG, "Max height reached."); finish = true; } @@ -150,8 +146,8 @@ public class ScrollCaptureController { return; } - int nextTop = (mDirection == DOWN) ? result.captured.bottom - : result.captured.top - mSession.getTileHeight(); + int nextTop = (mScrollingUp) + ? result.captured.top - mSession.getTileHeight() : result.captured.bottom; Log.d(TAG, "requestTile: " + nextTop); mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java index 9ed9659c7ab8..f2adaf042b2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java @@ -207,7 +207,7 @@ public class GestureRecorder { sb.append(g.toJson()); count++; } - mLastSaveLen += count; + mLastSaveLen = count; sb.append("]"); return sb.toString(); } @@ -249,9 +249,7 @@ public class GestureRecorder { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { save(); if (mLastSaveLen >= 0) { - pw.println(String.valueOf(mLastSaveLen) - + " gestures since last dump written to " + mLogfile); - mLastSaveLen = 0; + pw.println(String.valueOf(mLastSaveLen) + " gestures written to " + mLogfile); } else { pw.println("error writing gestures"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index d2ddd212bda6..5e8245f8e0e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -527,7 +527,7 @@ public class NotificationShelf extends ActivatableNotificationView implements handleCustomTransformHeight(view, expandingAnimated, iconState); float fullTransitionAmount; - float transitionAmount; + float iconTransitionAmount; float contentTransformationAmount; float shelfStart = getTranslationY(); boolean fullyInOrOut = true; @@ -549,18 +549,19 @@ public class NotificationShelf extends ActivatableNotificationView implements fullTransitionAmount = 1.0f - interpolatedAmount; if (isLastChild) { - // If it's the last child we should use all of the notification to transform - // instead of just to the icon, since that can be quite low. - transitionAmount = (shelfStart - viewStart) / transformDistance; + // Reduce icon transform distance to completely fade in shelf icon + // by the time the notification icon fades out, and vice versa + iconTransitionAmount = (shelfStart - viewStart) + / (iconTransformStart - viewStart); } else { - transitionAmount = (shelfStart - iconTransformStart) / transformDistance; + iconTransitionAmount = (shelfStart - iconTransformStart) / transformDistance; } - transitionAmount = MathUtils.constrain(transitionAmount, 0.0f, 1.0f); - transitionAmount = 1.0f - transitionAmount; + iconTransitionAmount = MathUtils.constrain(iconTransitionAmount, 0.0f, 1.0f); + iconTransitionAmount = 1.0f - iconTransitionAmount; fullyInOrOut = false; } else { fullTransitionAmount = 1.0f; - transitionAmount = 1.0f; + iconTransitionAmount = 1.0f; } // Transforming the content @@ -569,7 +570,7 @@ public class NotificationShelf extends ActivatableNotificationView implements contentTransformationAmount = 1.0f - contentTransformationAmount; } else { fullTransitionAmount = 0.0f; - transitionAmount = 0.0f; + iconTransitionAmount = 0.0f; contentTransformationAmount = 0.0f; } if (iconState != null && fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) { @@ -585,7 +586,7 @@ public class NotificationShelf extends ActivatableNotificationView implements view.setContentTransformationAmount(contentTransformationAmount, isLastChild); // Update the positioning of the icon - updateIconPositioning(view, transitionAmount, fullTransitionAmount, + updateIconPositioning(view, iconTransitionAmount, fullTransitionAmount, transformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild); return fullTransitionAmount; @@ -679,8 +680,7 @@ public class NotificationShelf extends ActivatableNotificationView implements || iconState.useLinearTransitionAmount) { transitionAmount = iconTransitionAmount; } else { - // We take the clamped position instead - transitionAmount = clampedAmount; + transitionAmount = iconTransitionAmount; iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount && !mNoAnimationsInThisFrame; } @@ -689,8 +689,7 @@ public class NotificationShelf extends ActivatableNotificationView implements ? fullTransitionAmount : transitionAmount; iconState.clampedAppearAmount = clampedAmount; - setIconTransformationAmount(view, transitionAmount, iconTransformDistance, - clampedAmount != transitionAmount, isLastChild); + setIconTransformationAmount(view, transitionAmount, isLastChild); } private boolean isTargetClipped(ExpandableView view) { @@ -708,7 +707,7 @@ public class NotificationShelf extends ActivatableNotificationView implements } private void setIconTransformationAmount(ExpandableView view, - float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation, + float transitionAmount, boolean isLastChild) { if (!(view instanceof ExpandableNotificationRow)) { return; @@ -720,42 +719,13 @@ public class NotificationShelf extends ActivatableNotificationView implements View rowIcon = row.getShelfTransformationTarget(); // Let's resolve the relative positions of the icons - float notificationIconSize = 0.0f; - int iconTopPadding; int iconStartPadding; if (rowIcon != null) { - iconTopPadding = row.getRelativeTopPadding(rowIcon); iconStartPadding = row.getRelativeStartPadding(rowIcon); - notificationIconSize = rowIcon.getHeight(); } else { - iconTopPadding = mIconAppearTopPadding; iconStartPadding = 0; } - - float shelfIconSize = mAmbientState.isFullyHidden() ? mHiddenShelfIconSize : mIconSize; - shelfIconSize = shelfIconSize * icon.getIconScale(); - - // Get the icon correctly positioned in Y - float notificationIconPositionY = row.getTranslationY() + row.getContentTranslation(); - float targetYPosition = 0; boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf(); - if (usingLinearInterpolation && !stayingInShelf) { - // If we interpolate from the notification position, this might lead to a slightly - // odd interpolation, since the notification position changes as well. - // Let's instead interpolate directly to the top left of the notification - targetYPosition = NotificationUtils.interpolate( - Math.min(notificationIconPositionY + mIconAppearTopPadding - - getTranslationY(), 0), - 0, - transitionAmount); - } - notificationIconPositionY += iconTopPadding; - float shelfIconPositionY = getTranslationY() + icon.getTop(); - shelfIconPositionY += (icon.getHeight() - shelfIconSize) / 2.0f; - float iconYTranslation = NotificationUtils.interpolate( - notificationIconPositionY - shelfIconPositionY, - targetYPosition, - transitionAmount); // Get the icon correctly positioned in X // Even in RTL it's the left, since we're inverting the location in post @@ -767,28 +737,19 @@ public class NotificationShelf extends ActivatableNotificationView implements transitionAmount); // Let's handle the case that there's no Icon - float alpha = 1.0f; boolean noIcon = !row.isShowingIcon(); if (noIcon) { // The view currently doesn't have an icon, lets transform it in! - alpha = transitionAmount; - notificationIconSize = shelfIconSize / 2.0f; iconXTranslation = mShelfIcons.getActualPaddingStart(); } - // The notification size is different from the size in the shelf / statusbar - float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize, - transitionAmount); if (iconState != null) { - iconState.scaleX = newSize / shelfIconSize; - iconState.scaleY = iconState.scaleX; iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon); boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); if (isAppearing) { iconState.hidden = true; iconState.iconAppearAmount = 0.0f; } - iconState.alpha = alpha; - iconState.yTranslation = iconYTranslation; + iconState.alpha = transitionAmount; iconState.xTranslation = iconXTranslation; if (stayingInShelf) { iconState.iconAppearAmount = 1.0f; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 01d31039a749..e5a960e13e6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -83,6 +83,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi public static final int STATE_DOT = 1; public static final int STATE_HIDDEN = 2; + /** Maximum allowed width or height for an icon drawable */ + private static final int MAX_IMAGE_SIZE = 500; + private static final String TAG = "StatusBarIconView"; private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT = new FloatProperty<StatusBarIconView>("iconAppearAmount") { @@ -378,6 +381,13 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon); return false; } + + if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE + || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) { + Log.w(TAG, "Drawable is too large " + mIcon); + return false; + } + if (withClear) { setImageDrawable(null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index d1ab7ea55d57..004cf9968a77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -180,9 +180,6 @@ class ConversationNotificationManager @Inject constructor( } if (changed) { notificationGroupManager.updateIsolation(entry) - // ensure that the conversation icon isn't hidden - // (ex: if it was showing in the shelf) - entry.row?.updateIconVisibilities() } } } 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 8a22b9f6891f..dbd8580b751e 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 @@ -176,7 +176,6 @@ public final class NotificationEntry extends ListEntry { private int mBucket = BUCKET_ALERTING; @Nullable private Long mPendingAnimationDuration; private boolean mIsMarkedForUserTriggeredMovement; - private boolean mShelfIconVisible; private boolean mIsAlerting; public boolean mRemoteEditImeVisible; @@ -417,7 +416,6 @@ public final class NotificationEntry extends ListEntry { //TODO: This will go away when we have a way to bind an entry to a row public void setRow(ExpandableNotificationRow row) { this.row = row; - updateShelfIconVisibility(); } public ExpandableNotificationRowController getRowController() { @@ -938,19 +936,6 @@ public final class NotificationEntry extends ListEntry { return mIsMarkedForUserTriggeredMovement; } - /** Whether or not the icon for this notification is visible in the shelf. */ - public void setShelfIconVisible(boolean shelfIconVisible) { - if (row == null) return; - mShelfIconVisible = shelfIconVisible; - updateShelfIconVisibility(); - } - - private void updateShelfIconVisibility() { - if (row != null) { - row.setShelfIconVisible(mShelfIconVisible); - } - } - /** * Mark this entry for movement triggered by a user action (ex: changing the priorirty of a * conversation). This can then be used for custom animations. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index ba45f9a687ed..5375ac345e50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -95,11 +95,6 @@ class IconManager @Inject constructor( // Construct the shelf icon view. val shelfIcon = iconBuilder.createIconView(entry) shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE - - // TODO: This doesn't belong here - shelfIcon.setOnVisibilityChangedListener { newVisibility: Int -> - entry.setShelfIconVisible(newVisibility == View.VISIBLE) - } shelfIcon.visibility = View.INVISIBLE // Construct the aod icon view. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 0ad6507fb01e..dff97a679164 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.init +import android.content.Context +import android.provider.Settings import android.service.notification.StatusBarNotification import com.android.systemui.dagger.SysUISingleton import com.android.systemui.people.widget.PeopleSpaceWidgetManager @@ -58,6 +60,7 @@ import javax.inject.Inject */ @SysUISingleton class NotificationsControllerImpl @Inject constructor( + private val context: Context, private val featureFlags: FeatureFlags, private val notificationListener: NotificationListener, private val entryManager: NotificationEntryManager, @@ -129,7 +132,9 @@ class NotificationsControllerImpl @Inject constructor( entryManager.attach(notificationListener) } - if (featureFlags.isPeopleTileEnabled) { + val showPeopleSpace = Settings.Global.getInt(context.contentResolver, + Settings.Global.SHOW_PEOPLE_SPACE, 1) + if (showPeopleSpace == 1) { peopleSpaceWidgetManager.attach(notificationListener) } } 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 1251b58171da..0f23b770aacd 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 @@ -331,7 +331,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mHeadsupDisappearRunning; private View mChildAfterViewWhenDismissed; private View mGroupParentWhenDismissed; - private boolean mShelfIconVisible; private boolean mAboveShelf; private OnUserInteractionCallback mOnUserInteractionCallback; private NotificationGutsManager mNotificationGutsManager; @@ -568,7 +567,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // The public layouts expand button is always visible mPublicLayout.updateExpandButtons(true); updateLimits(); - updateIconVisibilities(); updateShelfIconColor(); updateRippleAllowed(); if (mUpdateBackgroundOnUpdate) { @@ -883,7 +881,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView setDistanceToTopRoundness(NO_ROUNDNESS); mNotificationParent.updateBackgroundForGroupState(); } - updateIconVisibilities(); updateBackgroundClipping(); } @@ -1481,21 +1478,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getShelfTransformationTarget() != null; } - /** - * Set the icons to be visible of this notification. - */ - public void setShelfIconVisible(boolean iconVisible) { - if (iconVisible != mShelfIconVisible) { - mShelfIconVisible = iconVisible; - updateIconVisibilities(); - } - } - - @Override - protected void onBelowSpeedBumpChanged() { - updateIconVisibilities(); - } - @Override protected void updateContentTransformation() { if (mExpandAnimationRunning) { @@ -1522,18 +1504,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Refreshes the visibility of notification icons */ - public void updateIconVisibilities() { - // The shelf icon is never hidden for children in groups - boolean visible = !isChildInGroup() && mShelfIconVisible; - for (NotificationContentView l : mLayouts) { - l.setShelfIconVisible(visible); - } - if (mChildrenContainer != null) { - mChildrenContainer.setShelfIconVisible(visible); - } - } - public void setIsLowPriority(boolean isLowPriority) { mIsLowPriority = isLowPriority; mPrivateLayout.setIsLowPriority(isLowPriority); @@ -2052,8 +2022,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationLaunchHeight, zProgress); setTranslationZ(translationZ); - float extraWidthForClipping = params.getWidth() - getWidth() - + MathUtils.lerp(0, mOutlineRadius * 2, params.getProgress()); + float extraWidthForClipping = params.getWidth() - getWidth(); setExtraWidthForClipping(extraWidthForClipping); int top = params.getTop(); float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress()); 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 76917612b910..88cf2db527f0 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,6 +22,8 @@ import android.annotation.Nullable; 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; @@ -30,6 +32,7 @@ import android.util.ArrayMap; 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; @@ -169,7 +172,6 @@ public class NotificationContentView extends FrameLayout { private int mContentHeightAtAnimationStart = UNDEFINED; private boolean mFocusOnVisibilityChange; private boolean mHeadsUpAnimatingAway; - private boolean mShelfIconVisible; private int mClipBottomAmount; private boolean mIsLowPriority; private boolean mIsContentExpandable; @@ -1234,6 +1236,7 @@ public class NotificationContentView extends FrameLayout { mCachedHeadsUpRemoteInput = null; } + private RemoteInputView applyRemoteInput(View view, NotificationEntry entry, boolean hasRemoteInput, PendingIntent existingPendingIntent, RemoteInputView cachedView, NotificationViewWrapper wrapper) { @@ -1271,6 +1274,15 @@ public class NotificationContentView extends FrameLayout { 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))); @@ -1342,12 +1354,10 @@ public class NotificationContentView extends FrameLayout { && isPersonWithShortcut && entry.getBubbleMetadata() != null; if (showButton) { - Drawable d = mContext.getResources().getDrawable(entry.isBubble() + // explicitly resolve drawable resource using SystemUI's theme + Drawable d = mContext.getDrawable(entry.isBubble() ? R.drawable.bubble_ic_stop_bubble : R.drawable.bubble_ic_create_bubble); - mContainingNotification.updateNotificationColor(); - final int tint = mContainingNotification.getNotificationColor(); - d.setTint(tint); String contentDescription = mContext.getResources().getString(entry.isBubble() ? R.string.notification_conversation_unbubble @@ -1381,9 +1391,8 @@ public class NotificationContentView extends FrameLayout { return; } + // explicitly resolve drawable resource using SystemUI's theme Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze); - mContainingNotification.updateNotificationColor(); - snoozeDrawable.setTint(mContainingNotification.getNotificationColor()); snoozeButton.setImageDrawable(snoozeDrawable); final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext) @@ -1729,23 +1738,6 @@ public class NotificationContentView extends FrameLayout { mFocusOnVisibilityChange = true; } - public void setShelfIconVisible(boolean iconsVisible) { - mShelfIconVisible = iconsVisible; - updateIconVisibilities(); - } - - private void updateIconVisibilities() { - if (mContractedWrapper != null) { - mContractedWrapper.setShelfIconVisible(mShelfIconVisible); - } - if (mHeadsUpWrapper != null) { - mHeadsUpWrapper.setShelfIconVisible(mShelfIconVisible); - } - if (mExpandedWrapper != null) { - mExpandedWrapper.setShelfIconVisible(mShelfIconVisible); - } - } - @Override public void onVisibilityAggregated(boolean isVisible) { super.onVisibilityAggregated(isVisible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java index 7248bcef621c..151840afcef1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java @@ -44,9 +44,10 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl private void updateImageTag(StatusBarNotification notification) { final Bundle extras = notification.getNotification().extras; - Icon overRiddenIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG); - if (overRiddenIcon != null) { - mPicture.setTag(ImageTransformState.ICON_TAG, overRiddenIcon); + Icon overriddenIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG); + if (overriddenIcon != null) { + mRightIcon.setTag(ImageTransformState.ICON_TAG, overriddenIcon); + mLeftIcon.setTag(ImageTransformState.ICON_TAG, overriddenIcon); } } } 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 905bccfa6cdf..fb0fdcccd4b1 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 @@ -132,20 +132,6 @@ class NotificationConversationTemplateViewWrapper constructor( ) } - override fun setShelfIconVisible(visible: Boolean) { - if (conversationLayout.isImportantConversation) { - if (conversationIconView.visibility != View.GONE) { - conversationIconView.isForceHidden = visible - // We don't want the small icon to be hidden by the extended wrapper, as force - // hiding the conversationIcon will already do that via its listener. - return - } - } else { - conversationIconView.isForceHidden = false - } - super.setShelfIconVisible(visible) - } - override fun getShelfTransformationTarget(): View? = if (conversationLayout.isImportantConversation) if (conversationIconView.visibility != View.GONE) 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 eb79e3c8a69a..bdafd232167d 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 @@ -313,12 +313,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override - public void setShelfIconVisible(boolean visible) { - super.setShelfIconVisible(visible); - mIcon.setForceHidden(visible); - } - - @Override public TransformState getCurrentState(int fadingView) { return mTransformationHelper.getCurrentState(fadingView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java index e9934c0053b0..e0b58125aabd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java @@ -51,7 +51,8 @@ import com.android.systemui.statusbar.notification.row.HybridNotificationView; public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper { private final int mFullHeaderTranslation; - protected ImageView mPicture; + protected ImageView mRightIcon; + protected ImageView mLeftIcon; private ProgressBar mProgressBar; private TextView mTitle; private TextView mText; @@ -140,9 +141,14 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp } private void resolveTemplateViews(StatusBarNotification notification) { - mPicture = mView.findViewById(com.android.internal.R.id.right_icon); - if (mPicture != null) { - mPicture.setTag(ImageTransformState.ICON_TAG, + mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon); + if (mRightIcon != null) { + mRightIcon.setTag(ImageTransformState.ICON_TAG, + notification.getNotification().getLargeIcon()); + } + mLeftIcon = mView.findViewById(com.android.internal.R.id.left_icon); + if (mLeftIcon != null) { + mLeftIcon.setTag(ImageTransformState.ICON_TAG, notification.getNotification().getLargeIcon()); } mTitle = mView.findViewById(com.android.internal.R.id.title); @@ -240,9 +246,9 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp resolveTemplateViews(row.getEntry().getSbn()); super.onContentUpdated(row); // With the modern templates, a large icon visually overlaps the header, so we can't - // simply hide the header -- just show the + // hide the header, we must show it. mCanHideHeader = mNotificationHeader != null - && (mPicture == null || mPicture.getVisibility() != VISIBLE); + && (mRightIcon == null || mRightIcon.getVisibility() != VISIBLE); if (row.getHeaderVisibleAmount() != DEFAULT_HEADER_VISIBLE_AMOUNT) { setHeaderVisibleAmount(row.getHeaderVisibleAmount()); } @@ -260,14 +266,15 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText); } - if (mPicture != null) { + if (mRightIcon != null) { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, - mPicture); + mRightIcon); } if (mProgressBar != null) { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar); } + addViewsTransformingToSimilar(mLeftIcon); } @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 89babf0835c6..9ced12d32d27 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 @@ -331,11 +331,6 @@ public abstract class NotificationViewWrapper implements TransformableView { return null; } - /** - * Set the shelf icon to be visible and hide our own icons. - */ - public void setShelfIconVisible(boolean shelfIconVisible) {} - public int getHeaderTranslation(boolean forceNoHeader) { return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 756fe6c5ba24..8446b4e6a3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -133,7 +133,7 @@ public class AmbientState { */ public static int getNotificationLaunchHeight(Context context) { int zDistance = getZDistanceBetweenElements(context); - return getBaseHeight(zDistance) * 2; + return NOTIFICATIONS_HAVE_SHADOWS ? 2 * getBaseHeight(zDistance) : 4 * zDistance; } /** 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 3833637e8542..d8ee102064e1 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 @@ -20,10 +20,12 @@ import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.drawable.ColorDrawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Pair; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.NotificationHeaderView; import android.view.View; @@ -33,6 +35,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.CachingIconView; +import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationGroupingUtil; @@ -103,6 +106,8 @@ public class NotificationChildrenContainer extends ViewGroup { private ViewGroup mCurrentHeader; private boolean mIsConversation; + private boolean mTintWithThemeAccent; + private boolean mShowGroupCountInExpander; private boolean mShowDividersWhenExpanded; private boolean mHideDividersDuringExpand; private int mTranslationForHeader; @@ -145,6 +150,10 @@ public class NotificationChildrenContainer extends ViewGroup { com.android.internal.R.dimen.notification_content_margin); mEnableShadowOnChildNotifications = res.getBoolean(R.bool.config_enableShadowOnChildNotifications); + mTintWithThemeAccent = + res.getBoolean(com.android.internal.R.bool.config_tintNotificationsWithTheme); + mShowGroupCountInExpander = + res.getBoolean(R.bool.config_showNotificationGroupCountInExpander); mShowDividersWhenExpanded = res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded); mHideDividersDuringExpand = @@ -229,7 +238,6 @@ public class NotificationChildrenContainer extends ViewGroup { mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec); } if (mNotificationHeaderLowPriority != null) { - headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec); } @@ -397,7 +405,20 @@ public class NotificationChildrenContainer extends ViewGroup { mGroupingUtil.updateChildrenAppearance(); } + private void setExpandButtonNumber(NotificationViewWrapper wrapper) { + View expandButton = wrapper == null + ? null : wrapper.getExpandButton(); + if (expandButton instanceof NotificationExpandButton) { + ((NotificationExpandButton) expandButton).setNumber(mUntruncatedChildCount); + } + } + public void updateGroupOverflow() { + if (mShowGroupCountInExpander) { + setExpandButtonNumber(mNotificationHeaderWrapper); + setExpandButtonNumber(mNotificationHeaderWrapperLowPriority); + return; + } int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); if (mUntruncatedChildCount > maxAllowedVisibleChildren) { int number = mUntruncatedChildCount - maxAllowedVisibleChildren; @@ -1201,8 +1222,21 @@ public class NotificationChildrenContainer extends ViewGroup { } public void onNotificationUpdated() { - mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, - mContainingNotification.getNotificationColor()); + if (mShowGroupCountInExpander) { + // The overflow number is not used, so its color is irrelevant; skip this + return; + } + int color = mContainingNotification.getNotificationColor(); + if (mTintWithThemeAccent) { + // We're using the theme accent, color with the accent color instead of the notif color + 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(); + } + mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color); } public int getPositionInLinearLayout(View childInGroup) { @@ -1225,21 +1259,6 @@ public class NotificationChildrenContainer extends ViewGroup { return 0; } - public void setShelfIconVisible(boolean iconVisible) { - if (mNotificationHeaderWrapper != null) { - CachingIconView icon = mNotificationHeaderWrapper.getIcon(); - if (icon != null) { - icon.setForceHidden(iconVisible); - } - } - if (mNotificationHeaderWrapperLowPriority != null) { - CachingIconView icon = mNotificationHeaderWrapperLowPriority.getIcon(); - if (icon != null) { - icon.setForceHidden(iconVisible); - } - } - } - public void setClipBottomAmount(int clipBottomAmount) { mClipBottomAmount = clipBottomAmount; updateChildrenClipping(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index e40c262765ea..204dd9f5e58c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -14,6 +14,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; + import android.content.Context; import android.content.res.Resources; import android.hardware.display.ColorDisplayManager; @@ -27,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.SecureSetting; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.statusbar.policy.CastController; @@ -41,6 +44,8 @@ import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; import java.util.Objects; +import javax.inject.Named; + /** * Manages which tiles should be automatically added to QS. */ @@ -69,6 +74,8 @@ public class AutoTileManager implements UserAwareController { private final ManagedProfileController mManagedProfileController; private final NightDisplayListener mNightDisplayListener; private final CastController mCastController; + private final ReduceBrightColorsController mReduceBrightColorsController; + private final boolean mIsReduceBrightColorsAvailable; private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>(); public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, @@ -79,7 +86,9 @@ public class AutoTileManager implements UserAwareController { DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, - CastController castController) { + CastController castController, + ReduceBrightColorsController reduceBrightColorsController, + @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { mContext = context; mHost = host; mSecureSettings = secureSettings; @@ -91,6 +100,8 @@ public class AutoTileManager implements UserAwareController { mManagedProfileController = managedProfileController; mNightDisplayListener = nightDisplayListener; mCastController = castController; + mReduceBrightColorsController = reduceBrightColorsController; + mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable; } /** @@ -124,9 +135,9 @@ public class AutoTileManager implements UserAwareController { if (!mAutoTracker.isAdded(CAST)) { mCastController.addCallback(mCastCallback); } - - // TODO(b/170970675): Set a listener/controller and callback for Reduce Bright Colors - // state changes. Call into ColorDisplayService to get availability/config status + if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) { + mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback); + } int settingsN = mAutoAddSettingList.size(); for (int i = 0; i < settingsN; i++) { @@ -143,6 +154,9 @@ public class AutoTileManager implements UserAwareController { if (ColorDisplayManager.isNightDisplayAvailable(mContext)) { mNightDisplayListener.setCallback(null); } + if (mIsReduceBrightColorsAvailable) { + mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback); + } mCastController.removeCallback(mCastCallback); int settingsN = mAutoAddSettingList.size(); for (int i = 0; i < settingsN; i++) { @@ -287,6 +301,24 @@ public class AutoTileManager implements UserAwareController { }; @VisibleForTesting + final ReduceBrightColorsController.Listener mReduceBrightColorsCallback = + new ReduceBrightColorsController.Listener() { + @Override + public void onActivated(boolean activated) { + if (activated) { + addReduceBrightColorsTile(); + } + } + + private void addReduceBrightColorsTile() { + if (mAutoTracker.isAdded(BRIGHTNESS)) return; + mHost.addTile(BRIGHTNESS); + mAutoTracker.setTileAdded(BRIGHTNESS); + mHandler.post(() -> mReduceBrightColorsController.removeCallback(this)); + } + }; + + @VisibleForTesting final CastController.Callback mCastCallback = new CastController.Callback() { @Override public void onCastDevicesChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 986333ce5010..80109cb7a06c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; -import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK; @@ -74,10 +73,6 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.controls.dagger.ControlsComponent; -import com.android.systemui.controls.ui.ControlsDialog; -import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.IntentButtonProvider; import com.android.systemui.plugins.IntentButtonProvider.IntentButton; @@ -130,7 +125,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private KeyguardAffordanceView mRightAffordanceView; private KeyguardAffordanceView mLeftAffordanceView; - private ImageView mAltLeftButton; + private ImageView mWalletButton; private ViewGroup mIndicationArea; private TextView mIndicationText; private TextView mIndicationTextBottom; @@ -179,11 +174,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private int mBurnInXOffset; private int mBurnInYOffset; private ActivityIntentHelper mActivityIntentHelper; - - private ControlsDialog mControlsDialog; - private ControlsComponent mControlsComponent; private int mLockScreenMode; - private BroadcastDispatcher mBroadcastDispatcher; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; public KeyguardBottomAreaView(Context context) { @@ -251,7 +242,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mOverlayContainer = findViewById(R.id.overlay_container); mRightAffordanceView = findViewById(R.id.camera_button); mLeftAffordanceView = findViewById(R.id.left_button); - mAltLeftButton = findViewById(R.id.alt_left_button); + mWalletButton = findViewById(R.id.wallet_button); mIndicationArea = findViewById(R.id.keyguard_indication_area); mIndicationText = findViewById(R.id.keyguard_indication_text); mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom); @@ -351,10 +342,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setLayoutParams(lp); updateLeftAffordanceIcon(); - lp = mAltLeftButton.getLayoutParams(); + lp = mWalletButton.getLayoutParams(); lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width); lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height); - mAltLeftButton.setLayoutParams(lp); + mWalletButton.setLayoutParams(lp); } private void updateRightAffordanceIcon() { @@ -427,11 +418,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setContentDescription(state.contentDescription); } - private void updateControlsVisibility() { - if (mDozing || mControlsComponent.getVisibility() != AVAILABLE) { - mAltLeftButton.setVisibility(GONE); + private void updateWalletVisibility() { + if (mDozing) { + mWalletButton.setVisibility(GONE); } else { - mAltLeftButton.setVisibility(VISIBLE); + mWalletButton.setVisibility(VISIBLE); } } @@ -699,8 +690,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void startFinishDozeAnimation() { long delay = 0; - if (mAltLeftButton.getVisibility() == View.VISIBLE) { - startFinishDozeAnimationElement(mAltLeftButton, delay); + if (mWalletButton.getVisibility() == View.VISIBLE) { + startFinishDozeAnimationElement(mWalletButton, delay); } if (mLeftAffordanceView.getVisibility() == View.VISIBLE) { startFinishDozeAnimationElement(mLeftAffordanceView, delay); @@ -774,14 +765,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL updateCameraVisibility(); updateLeftAffordanceIcon(); - updateControlsVisibility(); + updateWalletVisibility(); if (dozing) { mOverlayContainer.setVisibility(INVISIBLE); - if (mControlsDialog != null) { - mControlsDialog.dismiss(); - mControlsDialog = null; - } } else { mOverlayContainer.setVisibility(VISIBLE); if (animate) { @@ -811,7 +798,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setAlpha(alpha); mRightAffordanceView.setAlpha(alpha); mIndicationArea.setAlpha(alpha); - mAltLeftButton.setAlpha(alpha); + mWalletButton.setAlpha(alpha); } private class DefaultLeftButton implements IntentButton { @@ -884,38 +871,18 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL return insets; } - /** - * Show or hide controls, depending on the lock screen mode and controls - * availability. - */ - public void setupControls(ControlsComponent component, BroadcastDispatcher dispatcher) { - mControlsComponent = component; - mBroadcastDispatcher = dispatcher; - setupControls(); - } - - private void setupControls() { + private void setupWallet() { boolean inNewLayout = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; boolean settingEnabled = Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 1; - if (!inNewLayout || !settingEnabled || !mControlsComponent.isEnabled()) { - mAltLeftButton.setVisibility(View.GONE); + if (!inNewLayout || !settingEnabled) { + mWalletButton.setVisibility(View.GONE); return; } - mControlsComponent.getControlsListingController().get() - .addCallback(list -> { - if (!list.isEmpty()) { - mAltLeftButton.setImageDrawable(list.get(0).loadIcon()); - mAltLeftButton.setOnClickListener((v) -> { - ControlsUiController ui = mControlsComponent - .getControlsUiController().get(); - mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher) - .show(ui); - }); - } - updateControlsVisibility(); - }); + // TODO: add image + // mWalletButton.setImageDrawable(list.get(0).loadIcon()); + updateWalletVisibility(); } /** @@ -923,6 +890,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL */ public void onLockScreenModeChanged(int mode) { mLockScreenMode = mode; - setupControls(); + setupWallet(); } } 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 83c347b05012..ae14fa943a4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -89,10 +89,8 @@ import com.android.systemui.DejankUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeLog; @@ -245,7 +243,6 @@ public class NotificationPanelViewController extends PanelViewController { public void onLockScreenModeChanged(int mode) { mLockScreenMode = mode; mClockPositionAlgorithm.onLockScreenModeChanged(mode); - mKeyguardBottomArea.onLockScreenModeChanged(mode); } @Override @@ -304,7 +301,6 @@ public class NotificationPanelViewController extends PanelViewController { private final QSDetailDisplayer mQSDetailDisplayer; private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; - private final ControlsComponent mControlsComponent; // 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 @@ -520,7 +516,6 @@ public class NotificationPanelViewController extends PanelViewController { private NotificationShelfController mNotificationShelfController; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; - private BroadcastDispatcher mBroadcastDispatcher; private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() { @Override @@ -578,9 +573,7 @@ public class NotificationPanelViewController extends PanelViewController { UserManager userManager, MediaDataManager mediaDataManager, AmbientState ambientState, - FeatureFlags featureFlags, - ControlsComponent controlsComponent, - BroadcastDispatcher broadcastDispatcher) { + FeatureFlags featureFlags) { super(view, falsingManager, dozeLog, keyguardStateController, (SysuiStatusBarStateController) statusBarStateController, vibratorHelper, latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager, @@ -623,7 +616,6 @@ public class NotificationPanelViewController extends PanelViewController { mScrimController = scrimController; mUserManager = userManager; mMediaDataManager = mediaDataManager; - mControlsComponent = controlsComponent; pulseExpansionHandler.setPulseExpandAbortListener(() -> { if (mQs != null) { mQs.animateHeaderSlidingOut(); @@ -662,7 +654,6 @@ public class NotificationPanelViewController extends PanelViewController { mEntryManager = notificationEntryManager; mConversationNotificationManager = conversationNotificationManager; mAuthController = authController; - mBroadcastDispatcher = broadcastDispatcher; mView.setBackgroundColor(Color.TRANSPARENT); OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener(); @@ -972,7 +963,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper); mKeyguardBottomArea.setStatusBar(mStatusBar); mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete); - mKeyguardBottomArea.setupControls(mControlsComponent, mBroadcastDispatcher); } private void updateMaxDisplayedNotifications(boolean recompute) { 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 55744f94f2b0..b6ed3e50ed7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -27,7 +27,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; -import android.annotation.Nullable; import android.content.res.Configuration; import android.content.res.Resources; import android.os.SystemClock; @@ -1243,7 +1242,10 @@ public abstract class PanelViewController { mVelocityTracker.clear(); break; } - return false; + + // Finally, if none of the above cases applies, ensure that touches do not get handled + // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858) + return (mView.getVisibility() != View.VISIBLE); } @Override 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 b25fced6a212..0807f8aa5607 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -78,7 +78,6 @@ import android.media.AudioAttributes; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -277,8 +276,7 @@ public class StatusBar extends SystemUI implements DemoMode, public static final boolean DEBUG = false; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info - public static final boolean DEBUG_GESTURES = Build.IS_DEBUGGABLE; // TODO(b/178277858) - public static final boolean DEBUG_GESTURES_VERBOSE = true; + public static final boolean DEBUG_GESTURES = false; public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; public static final boolean DEBUG_CAMERA_LIFT = false; @@ -458,7 +456,9 @@ public class StatusBar extends SystemUI implements DemoMode, private final DisplayMetrics mDisplayMetrics; // XXX: gesture research - private GestureRecorder mGestureRec = null; + private final GestureRecorder mGestureRec = DEBUG_GESTURES + ? new GestureRecorder("/sdcard/statusbar_gestures.dat") + : null; private final ScreenPinningRequest mScreenPinningRequest; @@ -856,10 +856,6 @@ public class StatusBar extends SystemUI implements DemoMode, mActivityIntentHelper = new ActivityIntentHelper(mContext); DateTimeView.setReceiverHandler(timeTickHandler); - - if (DEBUG_GESTURES) { - mGestureRec = new GestureRecorder(mContext.getCacheDir() + "/statusbar_gestures.dat"); - } } @Override @@ -2271,7 +2267,7 @@ public class StatusBar extends SystemUI implements DemoMode, public boolean interceptTouchEvent(MotionEvent event) { if (DEBUG_GESTURES) { - if (DEBUG_GESTURES_VERBOSE || event.getActionMasked() != MotionEvent.ACTION_MOVE) { + if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, event.getActionMasked(), (int) event.getX(), (int) event.getY(), mDisabled1, mDisabled2); @@ -2696,6 +2692,10 @@ public class StatusBar extends SystemUI implements DemoMode, return mDisplay.getRotation(); } + int getDisplayId() { + return mDisplayId; + } + public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags) { startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, @@ -2721,7 +2721,7 @@ public class StatusBar extends SystemUI implements DemoMode, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(flags); int result = ActivityManager.START_CANCELED; - ActivityOptions options = new ActivityOptions(getActivityOptions( + ActivityOptions options = new ActivityOptions(getActivityOptions(mDisplayId, null /* remoteAnimation */)); options.setDisallowEnterPictureInPictureWhileLaunching( disallowEnterPictureInPictureWhileLaunching); @@ -4366,6 +4366,7 @@ public class StatusBar extends SystemUI implements DemoMode, executeActionDismissingKeyguard(() -> { try { intent.send(null, 0, null, null, null, null, getActivityOptions( + mDisplayId, mActivityLaunchAnimator.getLaunchAnimation(associatedView, isOccluded()))); } catch (PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. @@ -4387,15 +4388,41 @@ public class StatusBar extends SystemUI implements DemoMode, mMainThreadHandler.post(runnable); } - public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) { - return getDefaultActivityOptions(animationAdapter).toBundle(); + /** + * Returns an ActivityOptions bundle created using the given parameters. + * + * @param displayId The ID of the display to launch the activity in. Typically this would be the + * display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. + */ + public static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter) { + ActivityOptions options = getDefaultActivityOptions(animationAdapter); + options.setLaunchDisplayId(displayId); + options.setCallerDisplayId(displayId); + return options.toBundle(); } - public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter, - boolean isKeyguardShowing, long eventTime) { + /** + * Returns an ActivityOptions bundle created using the given parameters. + * + * @param displayId The ID of the display to launch the activity in. Typically this would be the + * display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. + * @param isKeyguardShowing Whether keyguard is currently showing. + * @param eventTime The event time in milliseconds since boot, not including sleep. See + * {@link ActivityOptions#setSourceInfo}. + */ + public static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter, boolean isKeyguardShowing, + long eventTime) { ActivityOptions options = getDefaultActivityOptions(animationAdapter); options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime); + options.setLaunchDisplayId(displayId); + options.setCallerDisplayId(displayId); return options.toBundle(); } @@ -4535,4 +4562,8 @@ public class StatusBar extends SystemUI implements DemoMode, public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) { mExpansionChangedListeners.add(listener); } + + public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) { + mExpansionChangedListeners.remove(listener); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 598addc68d2e..34673f2503ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -427,8 +427,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit intent.getCreatorPackage(), adapter); } long eventTime = row.getAndResetLastActionUpTime(); - Bundle options = eventTime > 0 ? getActivityOptions(adapter, - mKeyguardStateController.isShowing(), eventTime) : getActivityOptions(adapter); + Bundle options = eventTime > 0 + ? getActivityOptions( + mStatusBar.getDisplayId(), + adapter, + mKeyguardStateController.isShowing(), + eventTime) + : getActivityOptions(mStatusBar.getDisplayId(), adapter); int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, null, null, options); mMainThreadHandler.post(() -> { @@ -450,6 +455,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit int launchResult = TaskStackBuilder.create(mContext) .addNextIntentWithParentStack(intent) .startActivities(getActivityOptions( + mStatusBar.getDisplayId(), mActivityLaunchAnimator.getLaunchAnimation( row, mStatusBar.isOccluded())), new UserHandle(UserHandle.getUserId(appUid))); 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 38f3bc891394..59c1138431fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -94,15 +94,6 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie goingToFullShade, oldState); } - - @Override - public void onDozeAmountChanged(float linearAmount, float amount) { - if (DEBUG) { - Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f", - linearAmount, amount)); - } - setDarkAmount(amount); - } }; @Inject @@ -294,20 +285,6 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie } } - /** - * Set the amount (ratio) that the device has transitioned to doze. - * - * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. - */ - private void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; - if (darkAmount == mDarkAmount) { - return; - } - mDarkAmount = darkAmount; - mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); - } - private boolean isListAnimating() { return mKeyguardVisibilityHelper.isVisibilityAnimating(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index 8845a05cf6f5..5a80c05cc3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -205,7 +205,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS protected void onViewAttached() { if (DEBUG) Log.d(TAG, "onViewAttached"); mAdapter.registerDataSetObserver(mDataSetObserver); - mDataSetObserver.onChanged(); + mAdapter.notifyDataSetChanged(); mKeyguardUpdateMonitor.registerCallback(mInfoCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mScreenLifecycle.addObserver(mScreenObserver); @@ -373,14 +373,13 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. */ private void setDarkAmount(float darkAmount) { - boolean isAwake = darkAmount != 0; + boolean isFullyDozed = darkAmount == 1; if (darkAmount == mDarkAmount) { return; } mDarkAmount = darkAmount; mListView.setDarkAmount(darkAmount); - mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); - if (!isAwake) { + if (isFullyDozed) { closeSwitcherIfOpenAndNotSimple(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index 6c097bdb08d3..044f52fd689e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -383,8 +383,15 @@ public class MobileSignalController extends SignalController<MobileState, Mobile int qsTypeIcon = 0; IconState qsIcon = null; CharSequence description = null; + // Mobile icon will only be shown in the statusbar in 2 scenarios + // 1. Mobile is the default network, and it is validated + // 2. Mobile is the default network, it is not validated and there is no other + // non-Carrier WiFi networks available. + boolean maybeShowIcons = (mCurrentState.inetCondition == 1) + || (mCurrentState.inetCondition == 0 + && !mNetworkController.isNonCarrierWifiNetworkAvailable()); // Only send data sim callbacks to QS. - if (mCurrentState.dataSim && mCurrentState.isDefault) { + if (mCurrentState.dataSim && mCurrentState.isDefault && maybeShowIcons) { qsTypeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0; qsIcon = new IconState(mCurrentState.enabled @@ -397,7 +404,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile boolean activityOut = mCurrentState.dataConnected && !mCurrentState.carrierNetworkChangeMode && mCurrentState.activityOut; - showDataIcon &= mCurrentState.dataSim && mCurrentState.isDefault; + showDataIcon &= mCurrentState.dataSim && mCurrentState.isDefault && maybeShowIcons; boolean showTriangle = showDataIcon && !mCurrentState.airplaneMode; int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0; showDataIcon |= mCurrentState.roaming; 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 8eb1e6487046..fbdaf9cdae20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -547,6 +547,10 @@ public class NetworkControllerImpl extends BroadcastReceiver return mWifiSignalController.isCarrierMergedWifi(subId); } + boolean isNonCarrierWifiNetworkAvailable() { + return !mNoNetworksAvailable; + } + boolean isEthernetDefault() { return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET); } @@ -908,6 +912,11 @@ public class NetworkControllerImpl extends BroadcastReceiver return true; } + @VisibleForTesting + void setNoNetworksAvailable(boolean noNetworksAvailable) { + mNoNetworksAvailable = noNetworksAvailable; + } + private void updateAirplaneMode(boolean force) { boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 1); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 738cab15431a..5638503be198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -439,7 +439,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onAvailable(Network network) { - if (DEBUG) Log.d(TAG, "onAvailable " + network.netId); + if (DEBUG) Log.d(TAG, "onAvailable " + network.getNetId()); updateState(); fireCallbacks(); }; @@ -448,7 +448,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi // how long the VPN connection is held on to. @Override public void onLost(Network network) { - if (DEBUG) Log.d(TAG, "onLost " + network.netId); + if (DEBUG) Log.d(TAG, "onLost " + network.getNetId()); updateState(); fireCallbacks(); }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index 8d72c9c8810e..b9b62b415c00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -106,10 +106,18 @@ public class WifiSignalController extends if (mCurrentState.inetCondition == 0) { contentDescription += ("," + mContext.getString(R.string.data_connection_no_internet)); } - IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription); if (mProviderModel) { + // WiFi icon will only be shown in the statusbar in 2 scenarios + // 1. WiFi is the default network, and it is validated + // 2. WiFi is the default network, it is not validated and there is no other + // non-Carrier WiFi networks available. + boolean maybeShowIcons = (mCurrentState.inetCondition == 1) + || (mCurrentState.inetCondition == 0 + && !mNetworkController.isNonCarrierWifiNetworkAvailable()); + IconState statusIcon = new IconState( + wifiVisible && maybeShowIcons, getCurrentIconId(), contentDescription); IconState qsIcon = null; - if (mCurrentState.isDefault || (!mNetworkController.isRadioOn() + if ((mCurrentState.isDefault && maybeShowIcons) || (!mNetworkController.isRadioOn() && !mNetworkController.isEthernetDefault())) { qsIcon = new IconState(mCurrentState.connected, mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected @@ -123,6 +131,8 @@ public class WifiSignalController extends ); callback.setWifiIndicators(wifiIndicators); } else { + IconState statusIcon = new IconState( + wifiVisible, getCurrentIconId(), contentDescription); IconState qsIcon = new IconState(mCurrentState.connected, mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected : getQsCurrentIconId(), contentDescription); @@ -146,15 +156,25 @@ public class WifiSignalController extends if (mCurrentState.inetCondition == 0) { dataContentDescription = mContext.getString(R.string.data_connection_no_internet); } - boolean qsVisible = mCurrentState.enabled - && (mCurrentState.connected && mCurrentState.inetCondition == 1); - + // Mobile icon will only be shown in the statusbar in 2 scenarios + // 1. Mobile is the default network, and it is validated + // 2. Mobile is the default network, it is not validated and there is no other + // non-Carrier WiFi networks available. + boolean maybeShowIcons = (mCurrentState.inetCondition == 1) + || (mCurrentState.inetCondition == 0 + && !mNetworkController.isNonCarrierWifiNetworkAvailable()); + boolean sbVisible = mCurrentState.enabled && mCurrentState.connected + && maybeShowIcons && mCurrentState.isDefault; IconState statusIcon = - new IconState(qsVisible, getCurrentIconIdForCarrierWifi(), contentDescription); - int qsTypeIcon = mCurrentState.connected ? icons.qsDataType : 0; - int typeIcon = mCurrentState.connected ? icons.dataType : 0; - IconState qsIcon = new IconState( - mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), contentDescription); + new IconState(sbVisible, getCurrentIconIdForCarrierWifi(), contentDescription); + int typeIcon = sbVisible ? icons.dataType : 0; + int qsTypeIcon = 0; + IconState qsIcon = null; + if (sbVisible) { + qsTypeIcon = icons.qsDataType; + qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), + contentDescription); + } CharSequence description = mNetworkController.getNetworkNameForCarrierWiFi(mCurrentState.subId); MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 5d028454a417..f19228783b88 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -43,7 +43,6 @@ import android.util.Log; import androidx.annotation.NonNull; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -87,12 +86,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { protected static final int SECONDARY = 1; protected static final int NEUTRAL = 2; - // If lock screen wallpaper colors should also be considered when selecting the theme. - // Doing this has performance impact, given that overlays would need to be swapped when - // the device unlocks. - @VisibleForTesting - static final boolean USE_LOCK_SCREEN_WALLPAPER = false; - private final ThemeOverlayApplier mThemeManager; private final UserManager mUserManager; private final BroadcastDispatcher mBroadcastDispatcher; @@ -103,7 +96,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private final WallpaperManager mWallpaperManager; private final KeyguardStateController mKeyguardStateController; private final boolean mIsMonetEnabled; - private WallpaperColors mLockColors; private WallpaperColors mSystemColors; // If fabricated overlays were already created for the current theme. private boolean mNeedsOverlayCreation; @@ -117,6 +109,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private FabricatedOverlay mSecondaryOverlay; // Neutral system colors overlay private FabricatedOverlay mNeutralOverlay; + // If wallpaper color event will be accepted and change the UI colors. + private boolean mAcceptColorEvents = true; @Inject public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, @@ -146,13 +140,20 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + filter.addAction(Intent.ACTION_WALLPAPER_CHANGED); mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); - reevaluateSystemTheme(true /* forceReload */); + if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction()) + || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) { + if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); + reevaluateSystemTheme(true /* forceReload */); + } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) { + mAcceptColorEvents = true; + Log.i(TAG, "Allowing color events again"); + } } - }, filter, mBgExecutor, UserHandle.ALL); + }, filter, mMainExecutor, UserHandle.ALL); mSecureSettings.registerContentObserverForUser( Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), false, @@ -170,38 +171,22 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { // Upon boot, make sure we have the most up to date colors mBgExecutor.execute(() -> { - WallpaperColors lockColors = mWallpaperManager.getWallpaperColors( - WallpaperManager.FLAG_LOCK); WallpaperColors systemColor = mWallpaperManager.getWallpaperColors( WallpaperManager.FLAG_SYSTEM); mMainExecutor.execute(() -> { - if (USE_LOCK_SCREEN_WALLPAPER) { - mLockColors = lockColors; - } mSystemColors = systemColor; reevaluateSystemTheme(false /* forceReload */); }); }); - if (USE_LOCK_SCREEN_WALLPAPER) { - mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - if (mLockColors == null) { - return; - } - // It's possible that the user has a lock screen wallpaper. On this case we'll - // end up with different colors after unlocking. - reevaluateSystemTheme(false /* forceReload */); - } - }); - } mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> { - if (USE_LOCK_SCREEN_WALLPAPER && (which & WallpaperManager.FLAG_LOCK) != 0) { - mLockColors = wallpaperColors; - if (DEBUG) { - Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); - } + if (!mAcceptColorEvents) { + Log.i(TAG, "Wallpaper color event rejected: " + wallpaperColors); + return; } + if (wallpaperColors != null && mAcceptColorEvents) { + mAcceptColorEvents = false; + } + if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { mSystemColors = wallpaperColors; if (DEBUG) { @@ -213,10 +198,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } private void reevaluateSystemTheme(boolean forceReload) { - WallpaperColors currentColors = - mKeyguardStateController.isShowing() && mLockColors != null - ? mLockColors : mSystemColors; - + final WallpaperColors currentColors = mSystemColors; final int mainColor; final int accentCandidate; if (currentColors == null) { @@ -378,8 +360,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER); - pw.println("mLockColors=" + mLockColors); pw.println("mSystemColors=" + mSystemColors); pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor)); @@ -388,5 +368,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { pw.println("mNeutralOverlay=" + mNeutralOverlay); pw.println("mIsMonetEnabled=" + mIsMonetEnabled); pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation); + pw.println("mAcceptColorEvents=" + mAcceptColorEvents); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 25345d5c4b4c..5dc7006406ee 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -778,6 +778,12 @@ public class VolumeDialogImpl implements VolumeDialog, /** Animates away the ringer drawer. */ private void hideRingerDrawer() { + + // If the ringer drawer isn't present, don't try to hide it. + if (mRingerDrawerContainer == null) { + return; + } + // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we // don't want to be able to see it while it animates away. getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 4611fe03e157..78cd3a823aab 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -77,6 +77,8 @@ import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; 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.Transitions; @@ -373,6 +375,9 @@ public abstract class WMShellBaseModule { return new PipUiEventLogger(uiEventLogger, packageManager); } + @BindsOptionalOf + abstract PipTouchHandler optionalPipTouchHandler(); + // // Shell transitions // @@ -448,6 +453,22 @@ public abstract class WMShellBaseModule { @BindsOptionalOf abstract AppPairsController optionalAppPairs(); + // Starting window + + @WMSingleton + @Provides + static Optional<StartingSurface> provideStartingSurface( + StartingWindowController startingWindowController) { + return Optional.of(startingWindowController.asStartingSurface()); + } + + @WMSingleton + @Provides + static StartingWindowController provideStartingWindowController(Context context, + @ShellMainThread ShellExecutor mainExecutor) { + return new StartingWindowController(context, mainExecutor); + } + // // Task view factory // @@ -479,6 +500,8 @@ public abstract class WMShellBaseModule { Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, + Optional<StartingSurface> startingSurface, + Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, @ShellMainThread ShellExecutor mainExecutor) { @@ -488,6 +511,8 @@ public abstract class WMShellBaseModule { legacySplitScreenOptional, splitScreenOptional, appPairsOptional, + startingSurface, + pipTouchHandlerOptional, fullscreenTaskListener, transitions, mainExecutor); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 854be1f76722..1783fa4112b8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -17,6 +17,7 @@ package com.android.keyguard; 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; @@ -24,13 +25,20 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.Insets; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.testing.TestableResources; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowInsetsController; +import android.widget.FrameLayout; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import org.junit.Before; @@ -45,12 +53,21 @@ import org.mockito.junit.MockitoRule; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerTest extends SysuiTestCase { + private static final int SCREEN_WIDTH = 1600; + private static final int FAKE_MEASURE_SPEC = + View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH, View.MeasureSpec.EXACTLY); + + private static final SecurityMode ONE_HANDED_SECURITY_MODE = SecurityMode.PIN; + private static final SecurityMode TWO_HANDED_SECURITY_MODE = SecurityMode.Password; + + @Rule public MockitoRule mRule = MockitoJUnit.rule(); @Mock private WindowInsetsController mWindowInsetsController; + @Mock private KeyguardSecurityViewFlipper mSecurityViewFlipper; @@ -58,9 +75,18 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Before public void setup() { + // Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache + // the real references (rather than the TestableResources that this call creates). + mContext.ensureTestableResources(); + FrameLayout.LayoutParams securityViewFlipperLayoutParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); + when(mSecurityViewFlipper.getLayoutParams()).thenReturn(securityViewFlipperLayoutParams); mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; + mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } @Test @@ -69,4 +95,128 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(), any(), any()); } -}
\ No newline at end of file + + @Test + public void onMeasure_usesFullWidthWithoutOneHandedMode() { + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */false, + /* sysuiResourceCanUseOneHandedKeyguard= */ false, + ONE_HANDED_SECURITY_MODE); + + mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + + verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + } + + @Test + public void onMeasure_usesFullWidthWithDeviceFlagDisabled() { + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */false, + /* sysuiResourceCanUseOneHandedKeyguard= */ true, + ONE_HANDED_SECURITY_MODE); + + mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + } + + @Test + public void onMeasure_usesFullWidthWithSysUIFlagDisabled() { + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */true, + /* sysuiResourceCanUseOneHandedKeyguard= */ false, + ONE_HANDED_SECURITY_MODE); + + mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + } + + @Test + public void onMeasure_usesHalfWidthWithFlagsEnabled() { + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */true, + /* sysuiResourceCanUseOneHandedKeyguard= */ true, + ONE_HANDED_SECURITY_MODE); + + int halfWidthMeasureSpec = + View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY); + mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + + verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, FAKE_MEASURE_SPEC); + } + + @Test + public void onMeasure_usesFullWidthForFullScreenIme() { + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */true, + /* sysuiResourceCanUseOneHandedKeyguard= */ true, + TWO_HANDED_SECURITY_MODE); + + mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + } + + @Test + public void onMeasure_respectsViewInsets() { + int imeInsetAmount = 100; + int systemBarInsetAmount = 10; + + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */false, + /* sysuiResourceCanUseOneHandedKeyguard= */ false, + ONE_HANDED_SECURITY_MODE); + + Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); + Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); + + WindowInsets insets = new WindowInsets.Builder() + .setInsets(ime(), imeInset) + .setInsetsIgnoringVisibility(systemBars(), systemBarInset) + .build(); + + // It's reduced by the max of the systembar and IME, so just subtract IME inset. + int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec( + SCREEN_WIDTH - imeInsetAmount, View.MeasureSpec.EXACTLY); + + mKeyguardSecurityContainer.onApplyWindowInsets(insets); + mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, expectedHeightMeasureSpec); + } + + @Test + public void onMeasure_respectsViewInsets_largerSystembar() { + int imeInsetAmount = 0; + int systemBarInsetAmount = 10; + + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */false, + /* sysuiResourceCanUseOneHandedKeyguard= */ false, + ONE_HANDED_SECURITY_MODE); + + Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); + Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); + + WindowInsets insets = new WindowInsets.Builder() + .setInsets(ime(), imeInset) + .setInsetsIgnoringVisibility(systemBars(), systemBarInset) + .build(); + + int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec( + SCREEN_WIDTH - systemBarInsetAmount, View.MeasureSpec.EXACTLY); + + mKeyguardSecurityContainer.onApplyWindowInsets(insets); + mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, expectedHeightMeasureSpec); + } + + private void setUpKeyguard( + boolean deviceConfigCanUseOneHandedKeyguard, + boolean sysuiResourceCanUseOneHandedKeyguard, + SecurityMode securityMode) { + TestableResources testableResources = mContext.getOrCreateTestableResources(); + testableResources.addOverride(com.android.internal.R.bool.config_enableOneHandedKeyguard, + deviceConfigCanUseOneHandedKeyguard); + testableResources.addOverride(R.bool.can_use_one_handed_bouncer, + sysuiResourceCanUseOneHandedKeyguard); + mKeyguardSecurityContainer.updateLayoutForSecurityMode(securityMode); + } +} 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 700f101e44b8..fb778e813adf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -242,9 +242,18 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void registersViewForCallbacks() throws RemoteException { + public void registersAndUnregistersViewForCallbacks() throws RemoteException { + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, + IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD); + mFgExecutor.runAllReady(); verify(mStatusBarStateController).addCallback(mUdfpsController.mStatusBarStateListener); verify(mStatusBar).addExpansionChangedListener( mUdfpsController.mStatusBarExpansionListener); + + mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); + mFgExecutor.runAllReady(); + verify(mStatusBarStateController).removeCallback(mUdfpsController.mStatusBarStateListener); + verify(mStatusBar).removeExpansionChangedListener( + mUdfpsController.mStatusBarExpansionListener); } } 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 800d8593035d..f60fa099feaa 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 @@ -19,14 +19,21 @@ package com.android.systemui.people.widget; import static android.app.Notification.CATEGORY_MISSED_CALL; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY; +import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY; +import static android.app.people.ConversationStatus.ACTIVITY_GAME; +import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID; import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE; +import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; +import static com.android.systemui.people.PeopleSpaceUtils.USER_ID; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -38,12 +45,15 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.Person; import android.app.people.ConversationChannel; +import android.app.people.ConversationStatus; import android.app.people.IPeopleManager; +import android.app.people.PeopleManager; import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Icon; import android.net.Uri; @@ -56,7 +66,6 @@ import android.testing.AndroidTestingRunner; import androidx.preference.PreferenceManager; import androidx.test.filters.SmallTest; -import com.android.internal.appwidget.IAppWidgetService; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.people.PeopleSpaceUtils; @@ -76,7 +85,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; @SmallTest @@ -99,6 +110,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private static final String NOTIFICATION_CONTENT = "message text"; private static final Uri URI = Uri.parse("fake_uri"); private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android); + private static final String KEY = PeopleSpaceUtils.getKey(SHORTCUT_ID, TEST_PACKAGE_A, 0); private static final Person PERSON = new Person.Builder() .setName("name") .setKey("abc") @@ -121,12 +133,15 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Mock private NotificationListener mListenerService; - @Mock - private IAppWidgetService mIAppWidgetService; + @Mock private AppWidgetManager mAppWidgetManager; @Mock private IPeopleManager mIPeopleManager; + @Mock + private PeopleManager mPeopleManager; + @Mock + private LauncherApps mLauncherApps; @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor; @@ -136,13 +151,19 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private final NoManSimulator mNoMan = new NoManSimulator(); private final FakeSystemClock mClock = new FakeSystemClock(); + private PeopleSpaceWidgetProvider mProvider; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mLauncherApps = mock(LauncherApps.class); mManager = new PeopleSpaceWidgetManager(mContext); - mManager.setAppWidgetManager(mIAppWidgetService, mAppWidgetManager, mIPeopleManager); + mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager, + mLauncherApps); mManager.attach(mListenerService); + mProvider = new PeopleSpaceWidgetProvider(); + mProvider.setPeopleSpaceWidgetManager(mManager); verify(mListenerService).addNotificationHandler(mListenerCaptor.capture()); NotificationHandler serviceListener = requireNonNull(mListenerCaptor.getValue()); @@ -166,7 +187,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testDoNotUpdateAppWidgetIfNoWidgets() throws Exception { int[] widgetIdsArray = {}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbn = createNotification( OTHER_SHORTCUT_ID, /* isMessagingStyle = */ false, /* isMissedCall = */ false); @@ -182,7 +203,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testDoNotUpdateAppWidgetIfNoShortcutInfo() throws Exception { int[] widgetIdsArray = {}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); Notification notificationWithoutShortcut = new Notification.Builder(mContext) .setContentTitle("TEST_TITLE") @@ -206,7 +227,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testDoNotUpdateAppWidgetIfNoPackage() throws Exception { int[] widgetIdsArray = {}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbnWithoutPackageName = new SbnBuilder() .setNotification(createMessagingStyleNotification( @@ -224,7 +245,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testDoNotUpdateAppWidgetIfNonConversationChannelModified() throws Exception { int[] widgetIdsArray = {1}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); NotificationChannel channel = new NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, IMPORTANCE_DEFAULT); @@ -240,7 +261,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testUpdateAppWidgetIfConversationChannelModified() throws Exception { int[] widgetIdsArray = {1}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); NotificationChannel channel = new NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, IMPORTANCE_DEFAULT); @@ -257,7 +278,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testDoNotUpdateNotificationPostedIfDifferentShortcutId() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbn = createNotification( OTHER_SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false); @@ -277,7 +298,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbnWithDifferentPackageName = new SbnBuilder() .setNotification(createMessagingStyleNotification( @@ -298,7 +319,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testDoNotUpdateNotificationRemovedIfDifferentShortcutId() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbn = createNotification( OTHER_SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false); @@ -318,7 +339,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testDoNotUpdateNotificationRemovedIfDifferentPackageName() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbnWithDifferentPackageName = new SbnBuilder() .setNotification(createMessagingStyleNotification( @@ -339,9 +360,74 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + public void testDoNotUpdateStatusPostedIfDifferentShortcutId() throws Exception { + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + ConversationStatus status1 = new ConversationStatus.Builder(OTHER_SHORTCUT_ID, + ACTIVITY_GAME).setDescription("Playing a game!").build(); + ConversationStatus status2 = new ConversationStatus.Builder(OTHER_SHORTCUT_ID, + ACTIVITY_BIRTHDAY).build(); + ConversationChannel conversationChannel = getConversationWithShortcutId(OTHER_SHORTCUT_ID, + Arrays.asList(status1, status2)); + mManager.updateWidgetsWithConversationChanged(conversationChannel); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, never()) + .updateAppWidgetOptions(anyInt(), any()); + verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), + any()); + } + + @Test + public void testUpdateStatusPostedIfExistingTile() throws Exception { + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + ConversationStatus status = new ConversationStatus.Builder(SHORTCUT_ID, + ACTIVITY_GAME).setDescription("Playing a game!").build(); + ConversationChannel conversationChannel = getConversationWithShortcutId(SHORTCUT_ID, + Arrays.asList(status)); + mManager.updateWidgetsWithConversationChanged(conversationChannel); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = mBundleArgumentCaptor.getValue(); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + assertThat(tile.getStatuses()).containsExactly(status); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + } + + @Test + public void testUpdateStatusPostedOnTwoExistingTiles() throws Exception { + addSecondWidgetForPersonTile(); + + ConversationStatus status = new ConversationStatus.Builder(SHORTCUT_ID, + ACTIVITY_ANNIVERSARY).build(); + ConversationChannel conversationChannel = getConversationWithShortcutId(SHORTCUT_ID, + Arrays.asList(status)); + mManager.updateWidgetsWithConversationChanged(conversationChannel); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT), + any()); + } + + @Test public void testUpdateNotificationPostedIfExistingTile() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(createNotification( @@ -362,17 +448,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testUpdateNotificationPostedOnTwoExistingTiles() throws Exception { - Bundle options = new Bundle(); - options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); - when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) - .thenReturn(options); - // Set the same Person associated on another People Tile widget ID. - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT); - - int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, - SECOND_WIDGET_ID_WITH_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + addSecondWidgetForPersonTile(); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(createNotification( @@ -395,19 +471,9 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testUpdateNotificationOnExistingTileAfterRemovingTileForSamePerson() throws Exception { - Bundle options = new Bundle(); - options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); - when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) - .thenReturn(options); - // Set the same Person associated on another People Tile widget ID. - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT); + addSecondWidgetForPersonTile(); - int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, - SECOND_WIDGET_ID_WITH_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - - PeopleSpaceUtils.removeStorageForTile(mContext, SECOND_WIDGET_ID_WITH_SHORTCUT); + PeopleSpaceUtils.removeStorageForTile(mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(createNotification( SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false)) @@ -430,7 +496,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { public void testUpdateMissedCallNotificationWithoutContentPostedIfExistingTile() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() @@ -456,7 +522,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { public void testUpdateMissedCallNotificationWithContentPostedIfExistingTile() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() @@ -480,7 +546,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Test public void testUpdateNotificationRemovedIfExistingTile() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbn = createNotification( SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false); @@ -502,14 +568,82 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { any()); } + @Test + public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners() { + addSecondWidgetForPersonTile(); + mProvider.onUpdate(mContext, mAppWidgetManager, + new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT}); + + // Delete only one widget for the conversation. + mManager.deleteWidgets(new int[]{WIDGET_ID_WITH_SHORTCUT}); + + // Check deleted storage. + SharedPreferences widgetSp = mContext.getSharedPreferences( + String.valueOf(WIDGET_ID_WITH_SHORTCUT), + Context.MODE_PRIVATE); + assertThat(widgetSp.getString(PACKAGE_NAME, null)).isNull(); + assertThat(widgetSp.getString(SHORTCUT_ID, null)).isNull(); + assertThat(widgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); + assertThat(sp.getStringSet(KEY, new HashSet<>())).containsExactly( + String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT)); + // Check listener & shortcut caching remain for other widget. + verify(mPeopleManager, never()).unregisterConversationListener(any()); + verify(mLauncherApps, never()).uncacheShortcuts(eq(TEST_PACKAGE_A), + eq(Arrays.asList(SHORTCUT_ID)), eq(UserHandle.of(0)), + eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS)); + + // Delete all widgets for the conversation. + mProvider.onDeleted(mContext, new int[]{SECOND_WIDGET_ID_WITH_SHORTCUT}); + + // Check deleted storage. + SharedPreferences secondWidgetSp = mContext.getSharedPreferences( + String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT), + Context.MODE_PRIVATE); + assertThat(secondWidgetSp.getString(PACKAGE_NAME, null)).isNull(); + assertThat(secondWidgetSp.getString(SHORTCUT_ID, null)).isNull(); + assertThat(secondWidgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID); + assertThat(sp.getStringSet(KEY, new HashSet<>())).isEmpty(); + // Check listener is removed and shortcut is uncached. + verify(mPeopleManager, times(1)).unregisterConversationListener(any()); + verify(mLauncherApps, times(1)).uncacheShortcuts(eq(TEST_PACKAGE_A), + eq(Arrays.asList(SHORTCUT_ID)), eq(UserHandle.of(0)), + eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS)); + } + + /** + * Adds another widget for {@code PERSON_TILE} with widget ID: {@code + * SECOND_WIDGET_ID_WITH_SHORTCUT}. + */ + private void addSecondWidgetForPersonTile() { + Bundle options = new Bundle(); + options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); + when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) + .thenReturn(options); + // Set the same Person associated on another People Tile widget ID. + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT); + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, + SECOND_WIDGET_ID_WITH_SHORTCUT}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + } + /** * Returns a single conversation associated with {@code shortcutId}. */ private ConversationChannel getConversationWithShortcutId(String shortcutId) throws Exception { + return getConversationWithShortcutId(shortcutId, Arrays.asList()); + } + + /** + * Returns a single conversation associated with {@code shortcutId} and {@code statuses}. + */ + private ConversationChannel getConversationWithShortcutId(String shortcutId, + List<ConversationStatus> statuses) throws Exception { ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( "name").build(); ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null, - 0L, false); + 0L, false, false, statuses); return convo; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java index 3d53062d7d02..62cc9b7e3602 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java @@ -49,7 +49,7 @@ public class TileAdapterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); TestableLooper.get(this).runWithLooper(() -> mTileAdapter = - new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake())); + new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake(), /* qsFlag */false)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt new file mode 100644 index 000000000000..998e070009cc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.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.systemui.qs.tileimpl + +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import android.text.TextUtils +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class QSTileBaseViewTest : SysuiTestCase() { + + @Mock + private lateinit var iconView: QSIconView + + private lateinit var tileView: QSTileBaseView + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + tileView = QSTileBaseView(context, iconView, false) + } + + @Test + fun testSecondaryLabelNotModified_unavailable() { + val state = QSTile.State() + val testString = "TEST STRING" + state.state = Tile.STATE_UNAVAILABLE + state.secondaryLabel = testString + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) + } + + @Test + fun testSecondaryLabelNotModified_booleanInactive() { + val state = QSTile.BooleanState() + val testString = "TEST STRING" + state.state = Tile.STATE_INACTIVE + state.secondaryLabel = testString + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) + } + + @Test + fun testSecondaryLabelNotModified_booleanActive() { + val state = QSTile.BooleanState() + val testString = "TEST STRING" + state.state = Tile.STATE_ACTIVE + state.secondaryLabel = testString + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) + } + + @Test + fun testSecondaryLabelNotModified_availableNotBoolean_inactive() { + val state = QSTile.State() + state.state = Tile.STATE_INACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() + } + + @Test + fun testSecondaryLabelNotModified_availableNotBoolean_active() { + val state = QSTile.State() + state.state = Tile.STATE_ACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() + } + + @Test + fun testSecondaryLabelDescription_unavailable() { + val state = QSTile.State() + state.state = Tile.STATE_UNAVAILABLE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo( + context.getString(R.string.tile_unavailable) + ) + } + + @Test + fun testSecondaryLabelDescription_booleanInactive() { + val state = QSTile.BooleanState() + state.state = Tile.STATE_INACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo( + context.getString(R.string.switch_bar_off) + ) + } + + @Test + fun testSecondaryLabelDescription_booleanActive() { + val state = QSTile.BooleanState() + state.state = Tile.STATE_ACTIVE + state.secondaryLabel = "" + + tileView.handleStateChanged(state) + + assertThat(state.secondaryLabel as CharSequence).isEqualTo( + context.getString(R.string.switch_bar_on) + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index ffd747e09e23..880c290802df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -18,10 +18,12 @@ package com.android.systemui.qs.tiles; import static junit.framework.Assert.assertEquals; +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.os.Handler; -import android.provider.Settings; import android.service.quicksettings.Tile; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -34,9 +36,9 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; -import com.android.systemui.util.settings.FakeSettings; import org.junit.Before; import org.junit.Test; @@ -60,8 +62,9 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { private QSLogger mQSLogger; @Mock private UserTracker mUserTracker; + @Mock + private ReduceBrightColorsController mReduceBrightColorsController; - private FakeSettings mFakeSettings; private TestableLooper mTestableLooper; private ReduceBrightColorsTile mTile; @@ -72,24 +75,23 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); when(mHost.getContext()).thenReturn(mContext); - mFakeSettings = new FakeSettings(); mTile = new ReduceBrightColorsTile( true, + mReduceBrightColorsController, mHost, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), mMetricsLogger, mStatusBarStateController, mActivityStarter, - mQSLogger, - mUserTracker, - mFakeSettings + mQSLogger ); } @Test public void testNotActive() { + when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(false); mTile.refreshState(); mTestableLooper.processAllMessages(); @@ -100,33 +102,27 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { @Test public void testActive() { - mFakeSettings.putIntForUser( - Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, - 1, - mUserTracker.getUserId()); + when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(true); mTile.refreshState(); mTestableLooper.processAllMessages(); - assertActiveState(); + assertEquals(Tile.STATE_ACTIVE, mTile.getState().state); + assertEquals(mTile.getState().label.toString(), + mContext.getString(R.string.quick_settings_reduce_bright_colors_label)); } @Test - public void testActive_clicked_isActive() { + public void testActive_clicked_featureIsActivated() { + when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(false); mTile.refreshState(); mTestableLooper.processAllMessages(); // Validity check assertEquals(Tile.STATE_INACTIVE, mTile.getState().state); mTile.handleClick(); - mTile.refreshState(); - mTestableLooper.processAllMessages(); - assertActiveState(); + verify(mReduceBrightColorsController, times(1)) + .setReduceBrightColorsActivated(eq(true)); } - private void assertActiveState() { - assertEquals(Tile.STATE_ACTIVE, mTile.getState().state); - assertEquals(mTile.getState().label.toString(), - mContext.getString(R.string.quick_settings_reduce_bright_colors_label)); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 71f146bf0220..f31639cef666 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -35,6 +35,7 @@ import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Icon; import android.os.UserHandle; @@ -121,4 +122,13 @@ public class StatusBarIconViewTest extends SysuiTestCase { assertEquals("Transparent backgrounds should fallback to drawable color", color, mIconView.getStaticDrawableColor()); } + + @Test + public void testGiantImageNotAllowed() { + Bitmap largeBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888); + Icon icon = Icon.createWithBitmap(largeBitmap); + StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage", + icon, 0, 0, ""); + assertFalse(mIconView.set(largeIcon)); + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 82d1f43e5e4e..094a70e24572 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -47,6 +49,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.SecureSetting; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; @@ -67,6 +70,8 @@ import org.mockito.MockitoAnnotations; import java.util.Collections; import java.util.List; +import javax.inject.Named; + @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -88,9 +93,11 @@ public class AutoTileManagerTest extends SysuiTestCase { @Mock private DataSaverController mDataSaverController; @Mock private ManagedProfileController mManagedProfileController; @Mock private NightDisplayListener mNightDisplayListener; + @Mock private ReduceBrightColorsController mReduceBrightColorsController; @Mock(answer = Answers.RETURNS_SELF) private AutoAddTracker.Builder mAutoAddTrackerBuilder; @Mock private Context mUserContext; + private final boolean mIsReduceBrightColorsAvailable = true; private AutoTileManager mAutoTileManager; private SecureSettings mSecureSettings; @@ -130,7 +137,9 @@ public class AutoTileManagerTest extends SysuiTestCase { DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, - CastController castController) { + CastController castController, + ReduceBrightColorsController reduceBrightColorsController, + @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { return new AutoTileManager(context, autoAddTrackerBuilder, mQsTileHost, Handler.createAsync(TestableLooper.get(this).getLooper()), mSecureSettings, @@ -138,13 +147,15 @@ public class AutoTileManagerTest extends SysuiTestCase { dataSaverController, managedProfileController, nightDisplayListener, - castController); + castController, + reduceBrightColorsController, + isReduceBrightColorsAvailable); } private AutoTileManager createAutoTileManager(Context context) { return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController, mDataSaverController, mManagedProfileController, mNightDisplayListener, - mCastController); + mCastController, mReduceBrightColorsController, mIsReduceBrightColorsAvailable); } @Test @@ -157,9 +168,11 @@ public class AutoTileManagerTest extends SysuiTestCase { ManagedProfileController mPC = mock(ManagedProfileController.class); NightDisplayListener nDS = mock(NightDisplayListener.class); CastController cC = mock(CastController.class); + ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class); AutoTileManager manager = - createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC); + createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC, + true); verify(tracker, never()).initialize(); verify(hC, never()).addCallback(any()); @@ -167,6 +180,7 @@ public class AutoTileManagerTest extends SysuiTestCase { verify(mPC, never()).addCallback(any()); verify(nDS, never()).setCallback(any()); verify(cC, never()).addCallback(any()); + verify(rBC, never()).addCallback(any()); assertNull(manager.getSecureSettingForKey(TEST_SETTING)); assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT)); } @@ -207,6 +221,10 @@ public class AutoTileManagerTest extends SysuiTestCase { inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull()); } + InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController); + inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any()); + inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any()); + InOrder inOrderCast = inOrder(mCastController); inOrderCast.verify(mCastController).removeCallback(any()); inOrderCast.verify(mCastController).addCallback(any()); @@ -247,6 +265,10 @@ public class AutoTileManagerTest extends SysuiTestCase { inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull()); } + InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController); + inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any()); + inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any()); + InOrder inOrderCast = inOrder(mCastController); inOrderCast.verify(mCastController).removeCallback(any()); inOrderCast.verify(mCastController, never()).addCallback(any()); @@ -315,6 +337,18 @@ public class AutoTileManagerTest extends SysuiTestCase { verify(mQsTileHost, never()).addTile("night"); } + @Test + public void reduceBrightColorsTileAdded_whenActivated() { + mAutoTileManager.mReduceBrightColorsCallback.onActivated(true); + verify(mQsTileHost).addTile("reduce_brightness"); + } + + @Test + public void reduceBrightColorsTileNotAdded_whenDeactivated() { + mAutoTileManager.mReduceBrightColorsCallback.onActivated(false); + verify(mQsTileHost, never()).addTile("reduce_brightness"); + } + private static List<CastDevice> buildFakeCastDevice(boolean isCasting) { CastDevice cd = new CastDevice(); cd.state = isCasting ? CastDevice.STATE_CONNECTED : CastDevice.STATE_DISCONNECTED; 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 dd31f522b94d..ec5114e181e8 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 @@ -71,10 +71,8 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.doze.DozeLog; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; @@ -222,10 +220,6 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock - private ControlsComponent mControlsComponent; - @Mock - private BroadcastDispatcher mBroadcastDispatcher; - @Mock private AmbientState mAmbientState; @Mock private UserManager mUserManager; @@ -328,9 +322,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { mUserManager, mMediaDataManager, mAmbientState, - mFeatureFlags, - mControlsComponent, - mBroadcastDispatcher); + mFeatureFlags); mNotificationPanelViewController.initDependencies( mStatusBar, mNotificationShelfController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index e52b92648670..999d2822c928 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -50,11 +50,11 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.telephony.CellSignalStrength; import android.telephony.NetworkRegistrationInfo; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.testing.TestableLooper; @@ -106,7 +106,6 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkControllerImpl mNetworkController; protected MobileSignalController mMobileSignalController; - protected PhoneStateListener mPhoneStateListener; protected SignalStrength mSignalStrength; protected ServiceState mServiceState; protected TelephonyDisplayInfo mTelephonyDisplayInfo; @@ -250,8 +249,6 @@ public class NetworkControllerBaseTest extends SysuiTestCase { setDefaultSubId(mSubId); setSubscriptions(mSubId); mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId); - mPhoneStateListener = mMobileSignalController.mMobileStatusTracker.getPhoneStateListener(); - ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackArg = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); verify(mMockCm, atLeastOnce()) @@ -372,6 +369,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false); mNetworkCallback.onCapabilitiesChanged( mock(Network.class), new NetworkCapabilities(mNetCapabilities)); + mDefaultCallbackInWifiTracker.onCapabilitiesChanged( + mock(Network.class), new NetworkCapabilities(mNetCapabilities)); } else { mNetworkCallback.onLost(mock(Network.class)); } @@ -453,18 +452,16 @@ public class NetworkControllerBaseTest extends SysuiTestCase { private void updateSignalStrength() { Log.d(TAG, "Sending Signal Strength: " + mSignalStrength); - mPhoneStateListener.onSignalStrengthsChanged(mSignalStrength); + mMobileSignalController.mMobileStatusTracker.getTelephonyCallback() + .onSignalStrengthsChanged(mSignalStrength); } protected void updateServiceState() { Log.d(TAG, "Sending Service State: " + mServiceState); - mPhoneStateListener.onServiceStateChanged(mServiceState); - mPhoneStateListener.onDisplayInfoChanged(mTelephonyDisplayInfo); - } - - public void updateCallState(int state) { - // Inputs not currently used in NetworkControllerImpl. - mPhoneStateListener.onCallStateChanged(state, "0123456789"); + mMobileSignalController.mMobileStatusTracker.getTelephonyCallback() + .onServiceStateChanged(mServiceState); + mMobileSignalController.mMobileStatusTracker.getTelephonyCallback() + .onDisplayInfoChanged(mTelephonyDisplayInfo); } public void updateDataConnectionState(int dataState, int dataNetType) { @@ -476,16 +473,19 @@ public class NetworkControllerBaseTest extends SysuiTestCase { when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN)) .thenReturn(fakeRegInfo); when(mTelephonyDisplayInfo.getNetworkType()).thenReturn(dataNetType); - mPhoneStateListener.onDataConnectionStateChanged(dataState, dataNetType); + mMobileSignalController.mMobileStatusTracker.getTelephonyCallback() + .onDataConnectionStateChanged(dataState, dataNetType); } public void updateDataActivity(int dataActivity) { - mPhoneStateListener.onDataActivity(dataActivity); + mMobileSignalController.mMobileStatusTracker.getTelephonyCallback() + .onDataActivity(dataActivity); } public void setCarrierNetworkChange(boolean enable) { Log.d(TAG, "setCarrierNetworkChange(" + enable + ")"); - mPhoneStateListener.onCarrierNetworkChange(enable); + mMobileSignalController.mMobileStatusTracker.getTelephonyCallback() + .onCarrierNetworkChange(enable); } protected void verifyHasNoSims(boolean hasNoSimsVisible) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index c6812a26c20b..847030e83115 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -223,13 +223,14 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { setWifiEnabled(true); verifyLastWifiIcon(false, WifiIcons.WIFI_NO_NETWORK); + mNetworkController.setNoNetworksAvailable(false); setWifiStateForVcn(true, testSsid); setWifiLevelForVcn(0); - // Connected, but still not validated - does not show //verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]); - verifyLastMobileDataIndicatorsForVcn(false, 0, TelephonyIcons.ICON_CWF, false); + verifyLastMobileDataIndicatorsForVcn(false, 0, 0, false); + mNetworkController.setNoNetworksAvailable(true); for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) { setWifiLevelForVcn(testLevel); @@ -239,7 +240,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { setConnectivityViaBroadcastForVcn( NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo); - verifyLastMobileDataIndicatorsForVcn(false, testLevel, TelephonyIcons.ICON_CWF, false); + verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index d80c40fcd07b..8a0ac1111b59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -19,13 +19,13 @@ package com.android.systemui.theme; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; -import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -33,6 +33,8 @@ import static org.mockito.Mockito.when; import android.app.WallpaperColors; import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Intent; import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.graphics.Color; @@ -91,7 +93,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Captor - private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; + private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver; @Captor private ArgumentCaptor<WallpaperManager.OnColorsChangedListener> mColorsListener; @@ -114,12 +116,10 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { }; mThemeOverlayController.start(); - if (USE_LOCK_SCREEN_WALLPAPER) { - verify(mKeyguardStateController).addCallback( - mKeyguardStateControllerCallback.capture()); - } verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null), eq(UserHandle.USER_ALL)); + verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(), + eq(mMainExecutor), any()); verify(mDumpManager).registerDumpable(any(), any()); } @@ -129,7 +129,6 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mBgExecutor).execute(registrationRunnable.capture()); registrationRunnable.getValue().run(); - verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_LOCK)); verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM)); } @@ -156,6 +155,18 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // Should not ask again if changed to same value mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); verifyNoMoreInteractions(mThemeOverlayApplier); + + // Should not ask again even for new colors until we change wallpapers + mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), + null, null), WallpaperManager.FLAG_SYSTEM); + verifyNoMoreInteractions(mThemeOverlayApplier); + + // But should change theme after changing wallpapers + clearInvocations(mThemeOverlayApplier); + mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), + null, null), WallpaperManager.FLAG_SYSTEM); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @Test diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 065e2bbd3eef..88e6b66e23a3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -102,6 +102,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ FingerprintGestureDispatcher.FingerprintGestureClient { private static final boolean DEBUG = false; private static final String LOG_TAG = "AbstractAccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CONNECTION = + LOG_TAG + ".IAccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CLIENT = + LOG_TAG + ".IAccessibilityServiceClient"; private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000; protected static final String TAKE_SCREENSHOT = "takeScreenshot"; @@ -127,6 +131,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ protected final Object mLock; protected final AccessibilitySecurityPolicy mSecurityPolicy; + protected final AccessibilityTrace mTrace; // The service that's bound to this instance. Whenever this value is non-null, this // object is registered as a death recipient @@ -247,7 +252,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport, - WindowManagerInternal windowManagerInternal, + AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager a11yWindowManager) { mContext = context; @@ -259,6 +264,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mSecurityPolicy = securityPolicy; mSystemActionPerformer = systemActionPerfomer; mSystemSupport = systemSupport; + mTrace = trace; mMainHandler = mainHandler; mInvocationHandler = new InvocationHandler(mainHandler.getLooper()); mA11yWindowManager = a11yWindowManager; @@ -291,6 +297,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return false; } try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onKeyEvent", + keyEvent + ", " + sequenceNumber); + } mServiceInterface.onKeyEvent(keyEvent, sequenceNumber); } catch (RemoteException e) { return false; @@ -354,11 +364,18 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setOnKeyEventResult(boolean handled, int sequence) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setOnKeyEventResult", + "handled=" + handled + ";sequence=" + sequence); + } mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence); } @Override public AccessibilityServiceInfo getServiceInfo() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getServiceInfo"); + } synchronized (mLock) { return mAccessibilityServiceInfo; } @@ -375,6 +392,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setServiceInfo(AccessibilityServiceInfo info) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setServiceInfo", "info=" + info); + } final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -400,6 +420,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Nullable @Override public AccessibilityWindowInfo.WindowListSparseArray getWindows() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindows"); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return null; @@ -434,6 +457,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public AccessibilityWindowInfo getWindow(int windowId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindow", "windowId=" + windowId); + } synchronized (mLock) { int displayId = Display.INVALID_DISPLAY; if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { @@ -469,6 +495,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, String viewIdResName, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByViewId", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";viewIdResName=" + viewIdResName + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -530,6 +563,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByText", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";text=" + text + ";interactionId=" + interactionId + + ";callback=" + callback + ";interrogatingTid=" + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -591,6 +630,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid, Bundle arguments) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace( + TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfoByAccessibilityId", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";interactionId=" + interactionId + ";callback=" + + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid + + ";arguments=" + arguments); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -652,6 +699,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findFocus", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";focusType=" + focusType + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -713,6 +767,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".focusSearch", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";direction=" + direction + ";interactionId=" + + interactionId + ";callback=" + callback + ";interrogatingTid=" + + interrogatingTid); + } final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); @@ -770,10 +831,18 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void sendGesture(int sequence, ParceledListSlice gestureSteps) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".sendGesture", + "sequence=" + sequence + ";gestureSteps=" + gestureSteps); + } } @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".dispatchGesture", "sequence=" + + sequence + ";gestureSteps=" + gestureSteps + ";displayId=" + displayId); + } } @Override @@ -781,6 +850,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performAccessibilityAction", + "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments + + ";interactionId=" + interactionId + ";callback=" + callback + + ";interrogatingTid=" + interrogatingTid); + } final int resolvedWindowId; synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { @@ -802,6 +878,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean performGlobalAction(int action) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performGlobalAction", + "action=" + action); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -812,6 +892,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSystemActions"); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return Collections.emptyList(); @@ -822,6 +905,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean isFingerprintGestureDetectionAvailable() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace( + TRACE_A11Y_SERVICE_CONNECTION + ".isFingerprintGestureDetectionAvailable"); + } if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { return false; } @@ -835,6 +922,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationScale(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationScale", + "displayId=" + displayId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return 1.0f; @@ -850,6 +941,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public Region getMagnificationRegion(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationRegion", + "displayId=" + displayId); + } synchronized (mLock) { final Region region = Region.obtain(); if (!hasRightsToCurrentUserLocked()) { @@ -874,6 +969,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterX(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterX", + "displayId=" + displayId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return 0.0f; @@ -896,6 +995,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterY(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterY", + "displayId=" + displayId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return 0.0f; @@ -928,6 +1031,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean resetMagnification(int displayId, boolean animate) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".resetMagnification", + "displayId=" + displayId + ";animate=" + animate); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -950,6 +1057,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationScaleAndCenter", + "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX + + ";centerY=" + centerY + ";animate=" + animate); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -974,6 +1086,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setMagnificationCallbackEnabled(int displayId, boolean enabled) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationCallbackEnabled", + "displayId=" + displayId + ";enabled=" + enabled); + } mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled); } @@ -983,11 +1099,19 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setSoftKeyboardCallbackEnabled(boolean enabled) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardCallbackEnabled", + "enabled=" + enabled); + } mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled); } @Override public void takeScreenshot(int displayId, RemoteCallback callback) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".takeScreenshot", + "displayId=" + displayId + ";callback=" + callback); + } final long currentTimestamp = SystemClock.uptimeMillis(); if (mRequestTakeScreenshotTimestampMs != 0 && (currentTimestamp - mRequestTakeScreenshotTimestampMs) @@ -1157,6 +1281,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ @Override public IBinder getOverlayWindowToken(int displayId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getOverlayWindowToken", + "displayId=" + displayId); + } synchronized (mLock) { return mOverlayWindowTokens.get(displayId); } @@ -1170,6 +1298,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ @Override public int getWindowIdForLeashToken(@NonNull IBinder token) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindowIdForLeashToken", + "token=" + token); + } synchronized (mLock) { return mA11yWindowManager.getWindowIdLocked(token); } @@ -1181,6 +1313,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // Clear the proxy in the other process so this // IAccessibilityServiceConnection can be garbage collected. if (mServiceInterface != null) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", "null, " + mId + ", null"); + } mServiceInterface.init(null, mId, null); } } catch (RemoteException re) { @@ -1329,6 +1464,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityEvent", + event + ";" + serviceWantsEvent); + } listener.onAccessibilityEvent(event, serviceWantsEvent); if (DEBUG) { Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); @@ -1382,6 +1521,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onMagnificationChanged", displayId + + ", " + region + ", " + scale + ", " + centerX + ", " + centerY); + } listener.onMagnificationChanged(displayId, region, scale, centerX, centerY); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re); @@ -1397,6 +1540,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSoftKeyboardShowModeChanged", + String.valueOf(showState)); + } listener.onSoftKeyboardShowModeChanged(showState); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService, @@ -1409,6 +1556,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonClicked", + String.valueOf(displayId)); + } listener.onAccessibilityButtonClicked(displayId); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re); @@ -1427,6 +1578,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace( + TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonAvailabilityChanged", + String.valueOf(available)); + } listener.onAccessibilityButtonAvailabilityChanged(available); } catch (RemoteException re) { Slog.e(LOG_TAG, @@ -1440,6 +1596,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onGesture", + gestureInfo.toString()); + } listener.onGesture(gestureInfo); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo @@ -1452,6 +1612,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSystemActionsChanged"); + } listener.onSystemActionsChanged(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending system actions change to " + mService, @@ -1464,6 +1627,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".clearAccessibilityCache"); + } listener.clearAccessibilityCache(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error during requesting accessibility info cache" @@ -1790,14 +1956,27 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setGestureDetectionPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region); } @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setTouchExplorationPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region); } @Override - public void setFocusAppearance(int strokeWidth, int color) { } + public void setFocusAppearance(int strokeWidth, int color) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setFocusAppearance", + "strokeWidth=" + strokeWidth + ";color=" + color); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index c63c2e1a257d..b3be0448edaf 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -149,6 +149,7 @@ import java.util.function.Predicate; */ public class AccessibilityManagerService extends IAccessibilityManager.Stub implements AbstractAccessibilityServiceConnection.SystemSupport, + AccessibilityTrace, AccessibilityUserState.ServiceInfoChangeListener, AccessibilityWindowManager.AccessibilityEventSender, AccessibilitySecurityPolicy.AccessibilityUserManager, @@ -243,6 +244,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock); + private final WindowManagerInternal.AccessibilityControllerInternal mA11yController; private int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -288,6 +290,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext = context; mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); + mA11yController = mWindowManagerService.getAccessibilityController(); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = packageManager; @@ -308,6 +311,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext = context; mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); + mA11yController = mWindowManagerService.getAccessibilityController(); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = mContext.getPackageManager(); @@ -328,16 +332,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public int getCurrentUserIdLocked() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getCurrentUserIdLocked"); + } return mCurrentUserId; } @Override public boolean isAccessibilityButtonShown() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".isAccessibilityButtonShown"); + } return mIsAccessibilityButtonShown; } @Override public void onServiceInfoChangedLocked(AccessibilityUserState userState) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState); + } scheduleNotifyClientsOfServicesStateChangeLocked(userState); } @@ -395,6 +408,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub PackageMonitor monitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onSomePackagesChanged"); + } + synchronized (mLock) { // Only the profile parent can install accessibility services. // Therefore we ignore packages from linked profiles. @@ -419,6 +436,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // mBindingServices in binderDied() during updating. Remove services from this // package from mBindingServices, and then update the user state to re-bind new // versions of them. + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onPackageUpdateFinished", + "packageName=" + packageName + ";uid=" + uid); + } synchronized (mLock) { final int userId = getChangingUserId(); if (userId != mCurrentUserId) { @@ -448,6 +469,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onPackageRemoved(String packageName, int uid) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onPackageRemoved", + "packageName=" + packageName + ";uid=" + uid); + } + synchronized (mLock) { final int userId = getChangingUserId(); // Only the profile parent can install accessibility services. @@ -487,6 +513,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".PM.onHandleForceStop", "intent=" + intent + ";packages=" + + packages + ";uid=" + uid + ";doit=" + doit); + } synchronized (mLock) { final int userId = getChangingUserId(); // Only the profile parent can install accessibility services. @@ -533,6 +563,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".BR.onReceive", "context=" + context + ";intent=" + intent); + } + String action = intent.getAction(); if (Intent.ACTION_USER_SWITCHED.equals(action)) { switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); @@ -616,6 +650,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public long addClient(IAccessibilityManagerClient callback, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".addClient", "callback=" + callback + ";userId=" + userId); + } + synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -654,6 +692,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".sendAccessibilityEvent", "event=" + event + ";userId=" + userId); + } boolean dispatchEvent = false; synchronized (mLock) { @@ -746,6 +787,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void registerSystemAction(RemoteAction action, int actionId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".registerSystemAction", "action=" + action + ";actionId=" + + actionId); + } mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); getSystemActionPerformer().registerSystemAction(actionId, action); } @@ -757,6 +802,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void unregisterSystemAction(int actionId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".unregisterSystemAction", "actionId=" + actionId); + } mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); getSystemActionPerformer().unregisterSystemAction(actionId); } @@ -771,6 +819,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getInstalledAccessibilityServiceList", "userId=" + userId); + } + synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -788,6 +840,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getEnabledAccessibilityServiceList", + "feedbackType=" + feedbackType + ";userId=" + userId); + } + synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -816,6 +873,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void interrupt(int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".interrupt", "userId=" + userId); + } + List<IAccessibilityServiceClient> interfacesToInterrupt; synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles @@ -842,6 +903,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) { try { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt"); + } interfacesToInterrupt.get(i).onInterrupt(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending interrupt request to " @@ -854,18 +918,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, IAccessibilityInteractionConnection connection, String packageName, int userId) throws RemoteException { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".addAccessibilityInteractionConnection", + "windowToken=" + windowToken + "leashToken=" + leashToken + ";connection=" + + connection + "; packageName=" + packageName + ";userId=" + userId); + } + return mA11yWindowManager.addAccessibilityInteractionConnection( windowToken, leashToken, connection, packageName, userId); } @Override public void removeAccessibilityInteractionConnection(IWindow window) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection", "window=" + window); + } mA11yWindowManager.removeAccessibilityInteractionConnection(window); } @Override public void setPictureInPictureActionReplacingConnection( IAccessibilityInteractionConnection connection) throws RemoteException { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection", + "connection=" + connection); + } mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA, SET_PIP_ACTION_REPLACEMENT); mA11yWindowManager.setPictureInPictureActionReplacingConnection(connection); @@ -876,13 +953,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub IAccessibilityServiceClient serviceClient, AccessibilityServiceInfo accessibilityServiceInfo, int flags) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".registerUiTestAutomationService", "owner=" + owner + + ";serviceClient=" + serviceClient + ";accessibilityServiceInfo=" + + accessibilityServiceInfo + ";flags=" + flags); + } + mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); synchronized (mLock) { mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient, mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler, - mSecurityPolicy, this, mWindowManagerService, getSystemActionPerformer(), + mSecurityPolicy, this, this, mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, flags); onUserStateChangedLocked(getCurrentUserStateLocked()); } @@ -890,6 +973,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".unregisterUiTestAutomationService", + "serviceClient=" + serviceClient); + } synchronized (mLock) { mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient); } @@ -898,6 +985,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void temporaryEnableAccessibilityStateUntilKeyguardRemoved( ComponentName service, boolean touchExplorationEnabled) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved", + "service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled); + } + mSecurityPolicy.enforceCallingPermission( Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY, TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED); @@ -926,6 +1018,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public IBinder getWindowToken(int windowId, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getWindowToken", "windowId=" + windowId + ";userId=" + userId); + } + mSecurityPolicy.enforceCallingPermission( Manifest.permission.RETRIEVE_WINDOW_TOKEN, GET_WINDOW_TOKEN); @@ -965,6 +1061,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void notifyAccessibilityButtonClicked(int displayId, String targetName) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked", + "displayId=" + displayId + ";targetName=" + targetName); + } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " @@ -990,6 +1091,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged", "shown=" + shown); + } + mSecurityPolicy.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR_SERVICE); synchronized (mLock) { @@ -1018,6 +1123,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void onSystemActionsChanged() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".onSystemActionsChanged"); + } + synchronized (mLock) { AccessibilityUserState state = getCurrentUserStateLocked(); notifySystemActionsChangedLocked(state); @@ -1080,6 +1189,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getMotionEventInjectorForDisplayLocked", "displayId=" + displayId); + } + final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS; MotionEventInjector motionEventInjector = null; while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) { @@ -1646,6 +1759,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void persistComponentNamesToSettingLocked(String settingName, Set<ComponentName> componentNames, int userId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".persistComponentNamesToSettingLocked", "settingName=" + settingName + + ";componentNames=" + componentNames + ";userId=" + userId); + } + persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames, componentName -> componentName.flattenToShortString()); } @@ -1730,7 +1848,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (service == null) { service = new AccessibilityServiceConnection(userState, mContext, componentName, installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, - this, mWindowManagerService, getSystemActionPerformer(), + this, this, mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService); } else if (userState.mBoundServices.contains(service)) { continue; @@ -2607,6 +2725,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @GuardedBy("mLock") @Override public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getCompatibleMagnificationSpecLocked", "windowId=" + windowId); + } + IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked( mCurrentUserId, windowId); if (windowToken != null) { @@ -2618,6 +2740,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public KeyEventDispatcher getKeyEventDispatcher() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getKeyEventDispatcher"); + } + if (mKeyEventDispatcher == null) { mKeyEventDispatcher = new KeyEventDispatcher( mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock, @@ -2630,6 +2756,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @SuppressWarnings("AndroidFrameworkPendingIntentMutability") public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent, int flags) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getPendingIntentActivity", "context=" + context + ";requestCode=" + + requestCode + ";intent=" + intent + ";flags=" + flags); + } + + return PendingIntent.getActivity(context, requestCode, intent, flags); } @@ -2644,6 +2776,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public void performAccessibilityShortcut(String targetName) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".performAccessibilityShortcut", "targetName=" + targetName); + } + if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY) != PackageManager.PERMISSION_GRANTED)) { @@ -2828,6 +2964,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getAccessibilityShortcutTargets", "shortcutType=" + shortcutType); + } + if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( @@ -2897,6 +3037,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".sendAccessibilityEventForCurrentUserLocked", "event=" + event); + } + sendAccessibilityEventLocked(event, mCurrentUserId); } @@ -2918,6 +3062,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public boolean sendFingerprintGesture(int gestureKeyCode) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".sendFingerprintGesture", "gestureKeyCode=" + gestureKeyCode); + } + synchronized(mLock) { if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { throw new SecurityException("Only SYSTEM can call sendFingerprintGesture"); @@ -2939,6 +3087,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public int getAccessibilityWindowId(@Nullable IBinder windowToken) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getAccessibilityWindowId", "windowToken=" + windowToken); + } + synchronized (mLock) { if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId"); @@ -2956,6 +3108,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ @Override public long getRecommendedTimeoutMillis() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getRecommendedTimeoutMillis"); + } + synchronized(mLock) { final AccessibilityUserState userState = getCurrentUserStateLocked(); return getRecommendedTimeoutMillisLocked(userState); @@ -2970,6 +3126,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setWindowMagnificationConnection( IWindowMagnificationConnection connection) throws RemoteException { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setWindowMagnificationConnection", "connection=" + connection); + } + mSecurityPolicy.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR_SERVICE); @@ -3000,6 +3160,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".associateEmbeddedHierarchy", + "host=" + host + ";embedded=" + embedded); + } + synchronized (mLock) { mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded); } @@ -3007,6 +3172,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", "token=" + token); + } + synchronized (mLock) { mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token); } @@ -3084,6 +3253,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public FullScreenMagnificationController getFullScreenMagnificationController() { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".getFullScreenMagnificationController"); + } synchronized (mLock) { return mMagnificationController.getFullScreenMagnificationController(); } @@ -3091,6 +3263,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onClientChangeLocked(boolean serviceInfoChanged) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".onClientChangeLocked", "serviceInfoChanged=" + serviceInfoChanged); + } + AccessibilityUserState userState = getUserStateLocked(mCurrentUserId); onUserStateChangedLocked(userState); if (serviceInfoChanged) { @@ -3126,8 +3302,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceConnection service = new AccessibilityServiceConnection( userState, mContext, COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, - AccessibilityManagerService.this, mWindowManagerService, - getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService) { + AccessibilityManagerService.this, AccessibilityManagerService.this, + mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager, + mActivityTaskManagerService) { @Override public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { return true; @@ -3614,6 +3791,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setGestureDetectionPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } + mMainHandler.sendMessage( obtainMessage( AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal, @@ -3624,6 +3806,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) { + if (isA11yTracingEnabled()) { + logTrace(LOG_TAG + ".setTouchExplorationPassthroughRegion", + "displayId=" + displayId + ";region=" + region); + } + mMainHandler.sendMessage( obtainMessage( AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal, @@ -3661,4 +3848,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } + + @Override + public boolean isA11yTracingEnabled() { + return mA11yController.isAccessibilityTracingEnabled(); + } + + @Override + public void logTrace(String where) { + logTrace(where, ""); + } + + @Override + public void logTrace(String where, String callingParams) { + mA11yController.logTrace(where, callingParams, "".getBytes(), + Binder.getCallingUid(), Thread.currentThread().getStackTrace()); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 675626841d17..7d75b738d818 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -53,6 +53,10 @@ import java.util.Set; */ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection { private static final String LOG_TAG = "AccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CONNECTION = + LOG_TAG + ".IAccessibilityServiceConnection"; + private static final String TRACE_A11Y_SERVICE_CLIENT = + LOG_TAG + ".IAccessibilityServiceClient"; /* Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps lists of bound and binding services. These are freed on user changes, but just in case it @@ -70,11 +74,12 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport, - WindowManagerInternal windowManagerInternal, + AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm, ActivityTaskManagerInternal activityTaskManagerService) { super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock, - securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm); + securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerfomer, + awm); mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState); mIntent = new Intent().setComponent(mComponentName); mMainHandler = mainHandler; @@ -132,6 +137,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void disableSelf() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".disableSelf"); + } synchronized (mLock) { AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState == null) return; @@ -210,6 +218,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return; } try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", this + ", " + mId + ", " + + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); + } serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } catch (RemoteException re) { Slog.w(LOG_TAG, "Error while setting connection for service: " @@ -252,6 +264,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean setSoftKeyboardShowMode(int showMode) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardShowMode", + "showMode=" + showMode); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -264,12 +280,19 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public int getSoftKeyboardShowMode() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSoftKeyboardShowMode"); + } final AccessibilityUserState userState = mUserStateWeakReference.get(); return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0; } @Override public boolean switchToInputMethod(String imeId) { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".switchToInputMethod", + "imeId=" + imeId); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -288,6 +311,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean isAccessibilityButtonAvailable() { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".isAccessibilityButtonAvailable"); + } synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return false; @@ -347,6 +373,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } if (serviceInterface != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + + ".onFingerprintCapturingGesturesChanged", String.valueOf(active)); + } mServiceInterface.onFingerprintCapturingGesturesChanged(active); } catch (RemoteException e) { } @@ -364,6 +394,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } if (serviceInterface != null) { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onFingerprintGesture", + String.valueOf(gesture)); + } mServiceInterface.onFingerprintGesture(gesture); } catch (RemoteException e) { } @@ -382,6 +416,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect gestureSteps.getList(), mServiceInterface, sequence, displayId); } else { try { + if (mTrace.isA11yTracingEnabled()) { + mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onPerformGestureResult", + sequence + ", false"); + } mServiceInterface.onPerformGestureResult(sequence, false); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending motion event injection failure to " diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java new file mode 100644 index 000000000000..0c03877d6e44 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +/** + * Interface to log accessibility trace. + */ +public interface AccessibilityTrace { + /** + * Whether the trace is enabled. + */ + boolean isA11yTracingEnabled(); + + /** + * Log one trace entry. + * @param where A string to identify this log entry, which can be used to filter/search + * through the tracing file. + */ + void logTrace(String where); + + /** + * Log one trace entry. + * @param where A string to identify this log entry, which can be used to filter/search + * through the tracing file. + * @param callingParams The parameters for the method to be logged. + */ + void logTrace(String where, String callingParams); +} diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java index bafb641dcc9e..6828dd916701 100644 --- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -40,29 +40,34 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection private final IAccessibilityInteractionConnectionCallback mServiceCallback; private final IAccessibilityInteractionConnection mConnectionWithReplacementActions; private final int mInteractionId; + private final int mNodeWithReplacementActionsInteractionId; private final Object mLock = new Object(); @GuardedBy("mLock") - List<AccessibilityNodeInfo> mNodesWithReplacementActions; + private boolean mReplacementNodeIsReadyOrFailed; + + @GuardedBy("mLock") + AccessibilityNodeInfo mNodeWithReplacementActions; @GuardedBy("mLock") List<AccessibilityNodeInfo> mNodesFromOriginalWindow; @GuardedBy("mLock") + boolean mSetFindNodeFromOriginalWindowCalled = false; + + @GuardedBy("mLock") AccessibilityNodeInfo mNodeFromOriginalWindow; - // Keep track of whether or not we've been called back for a single node @GuardedBy("mLock") - boolean mSingleNodeCallbackHappened; + boolean mSetFindNodesFromOriginalWindowCalled = false; + - // Keep track of whether or not we've been called back for multiple node @GuardedBy("mLock") - boolean mMultiNodeCallbackHappened; + List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow; - // We shouldn't get any more callbacks after we've called back the original service, but - // keep track to make sure we catch such strange things @GuardedBy("mLock") - boolean mDone; + boolean mSetPrefetchFromOriginalWindowCalled = false; + public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback, IAccessibilityInteractionConnection connectionWithReplacementActions, @@ -70,19 +75,20 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection mServiceCallback = serviceCallback; mConnectionWithReplacementActions = connectionWithReplacementActions; mInteractionId = interactionId; + mNodeWithReplacementActionsInteractionId = interactionId + 1; // Request the root node of the replacing window final long identityToken = Binder.clearCallingIdentity(); try { mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId( - AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0, + AccessibilityNodeInfo.ROOT_NODE_ID, null, + mNodeWithReplacementActionsInteractionId, this, 0, interrogatingPid, interrogatingTid, null, null); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); } - // Pretend we already got a (null) list of replacement nodes - mMultiNodeCallbackHappened = true; + mReplacementNodeIsReadyOrFailed = true; } finally { Binder.restoreCallingIdentity(identityToken); } @@ -90,46 +96,67 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection @Override public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) { - boolean readyForCallback; - synchronized(mLock) { + synchronized (mLock) { if (interactionId == mInteractionId) { mNodeFromOriginalWindow = info; + mSetFindNodeFromOriginalWindowCalled = true; + } else if (interactionId == mNodeWithReplacementActionsInteractionId) { + mNodeWithReplacementActions = info; + mReplacementNodeIsReadyOrFailed = true; } else { Slog.e(LOG_TAG, "Callback with unexpected interactionId"); return; } - - mSingleNodeCallbackHappened = true; - readyForCallback = mMultiNodeCallbackHappened; - } - if (readyForCallback) { - replaceInfoActionsAndCallService(); } + replaceInfoActionsAndCallServiceIfReady(); } @Override public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, int interactionId) { - boolean callbackForSingleNode; - boolean callbackForMultipleNodes; - synchronized(mLock) { + synchronized (mLock) { if (interactionId == mInteractionId) { mNodesFromOriginalWindow = infos; - } else if (interactionId == mInteractionId + 1) { - mNodesWithReplacementActions = infos; + mSetFindNodesFromOriginalWindowCalled = true; + } else if (interactionId == mNodeWithReplacementActionsInteractionId) { + setNodeWithReplacementActionsFromList(infos); + mReplacementNodeIsReadyOrFailed = true; } else { Slog.e(LOG_TAG, "Callback with unexpected interactionId"); return; } - callbackForSingleNode = mSingleNodeCallbackHappened; - callbackForMultipleNodes = mMultiNodeCallbackHappened; - mMultiNodeCallbackHappened = true; } - if (callbackForSingleNode) { - replaceInfoActionsAndCallService(); + replaceInfoActionsAndCallServiceIfReady(); + } + + @Override + public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos, + int interactionId) + throws RemoteException { + synchronized (mLock) { + if (interactionId == mInteractionId) { + mPrefetchedNodesFromOriginalWindow = infos; + mSetPrefetchFromOriginalWindowCalled = true; + } else { + Slog.e(LOG_TAG, "Callback with unexpected interactionId"); + return; + } } - if (callbackForMultipleNodes) { - replaceInfosActionsAndCallService(); + replaceInfoActionsAndCallServiceIfReady(); + } + + private void replaceInfoActionsAndCallServiceIfReady() { + replaceInfoActionsAndCallService(); + replaceInfosActionsAndCallService(); + replacePrefetchInfosActionsAndCallService(); + } + + private void setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos) { + for (int i = 0; i < infos.size(); i++) { + AccessibilityNodeInfo info = infos.get(i); + if (info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) { + mNodeWithReplacementActions = info; + } } } @@ -142,55 +169,81 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection private void replaceInfoActionsAndCallService() { final AccessibilityNodeInfo nodeToReturn; + boolean doCallback = false; synchronized (mLock) { - if (mDone) { - if (DEBUG) { - Slog.e(LOG_TAG, "Extra callback"); - } - return; - } - if (mNodeFromOriginalWindow != null) { + doCallback = mReplacementNodeIsReadyOrFailed + && mSetFindNodeFromOriginalWindowCalled; + if (doCallback && mNodeFromOriginalWindow != null) { replaceActionsOnInfoLocked(mNodeFromOriginalWindow); + mSetFindNodeFromOriginalWindowCalled = false; } - recycleReplaceActionNodesLocked(); nodeToReturn = mNodeFromOriginalWindow; - mDone = true; } - try { - mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); + if (doCallback) { + try { + mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); + } } } } private void replaceInfosActionsAndCallService() { - final List<AccessibilityNodeInfo> nodesToReturn; + List<AccessibilityNodeInfo> nodesToReturn = null; + boolean doCallback = false; synchronized (mLock) { - if (mDone) { + doCallback = mReplacementNodeIsReadyOrFailed + && mSetFindNodesFromOriginalWindowCalled; + if (doCallback) { + nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow); + mSetFindNodesFromOriginalWindowCalled = false; + } + } + if (doCallback) { + try { + mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); + } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Extra callback"); + Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); } - return; } - if (mNodesFromOriginalWindow != null) { - for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) { - replaceActionsOnInfoLocked(mNodesFromOriginalWindow.get(i)); + } + } + + private void replacePrefetchInfosActionsAndCallService() { + List<AccessibilityNodeInfo> nodesToReturn = null; + boolean doCallback = false; + synchronized (mLock) { + doCallback = mReplacementNodeIsReadyOrFailed + && mSetPrefetchFromOriginalWindowCalled; + if (doCallback) { + nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow); + mSetPrefetchFromOriginalWindowCalled = false; + } + } + if (doCallback) { + try { + mServiceCallback.setPrefetchAccessibilityNodeInfoResult( + nodesToReturn, mInteractionId); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); } } - recycleReplaceActionNodesLocked(); - nodesToReturn = (mNodesFromOriginalWindow == null) - ? null : new ArrayList<>(mNodesFromOriginalWindow); - mDone = true; } - try { - mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); + } + + @GuardedBy("mLock") + private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) { + if (infos != null) { + for (int i = 0; i < infos.size(); i++) { + replaceActionsOnInfoLocked(infos.get(i)); } } + return (infos == null) + ? null : new ArrayList<>(infos); } @GuardedBy("mLock") @@ -204,40 +257,22 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection info.setDismissable(false); // We currently only replace actions for the root node if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) - && mNodesWithReplacementActions != null) { - // This list should always contain a single node with the root ID - for (int i = 0; i < mNodesWithReplacementActions.size(); i++) { - AccessibilityNodeInfo nodeWithReplacementActions = - mNodesWithReplacementActions.get(i); - if (nodeWithReplacementActions.getSourceNodeId() - == AccessibilityNodeInfo.ROOT_NODE_ID) { - List<AccessibilityAction> actions = nodeWithReplacementActions.getActionList(); - if (actions != null) { - for (int j = 0; j < actions.size(); j++) { - info.addAction(actions.get(j)); - } - // The PIP needs to be able to take accessibility focus - info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); - info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); - } - info.setClickable(nodeWithReplacementActions.isClickable()); - info.setFocusable(nodeWithReplacementActions.isFocusable()); - info.setContextClickable(nodeWithReplacementActions.isContextClickable()); - info.setScrollable(nodeWithReplacementActions.isScrollable()); - info.setLongClickable(nodeWithReplacementActions.isLongClickable()); - info.setDismissable(nodeWithReplacementActions.isDismissable()); + && mNodeWithReplacementActions != null) { + List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList(); + if (actions != null) { + for (int j = 0; j < actions.size(); j++) { + info.addAction(actions.get(j)); } + // The PIP needs to be able to take accessibility focus + info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); + info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } + info.setClickable(mNodeWithReplacementActions.isClickable()); + info.setFocusable(mNodeWithReplacementActions.isFocusable()); + info.setContextClickable(mNodeWithReplacementActions.isContextClickable()); + info.setScrollable(mNodeWithReplacementActions.isScrollable()); + info.setLongClickable(mNodeWithReplacementActions.isLongClickable()); + info.setDismissable(mNodeWithReplacementActions.isDismissable()); } } - - @GuardedBy("mLock") - private void recycleReplaceActionNodesLocked() { - if (mNodesWithReplacementActions == null) return; - for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) { - AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i); - nodeWithReplacementAction.recycle(); - } - mNodesWithReplacementActions = null; - } } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 4473754e2b68..9547280018e4 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -53,6 +53,8 @@ class UiAutomationManager { private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport; + private AccessibilityTrace mTrace; + private int mUiAutomationFlags; UiAutomationManager(Object lock) { @@ -89,6 +91,7 @@ class UiAutomationManager { int id, Handler mainHandler, AccessibilitySecurityPolicy securityPolicy, AbstractAccessibilityServiceConnection.SystemSupport systemSupport, + AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm, int flags) { @@ -111,13 +114,14 @@ class UiAutomationManager { mUiAutomationFlags = flags; mSystemSupport = systemSupport; + mTrace = trace; // Ignore registering UiAutomation if it is not allowed to use the accessibility // subsystem. if (!useAccessibility()) { return; } mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id, - mainHandler, mLock, securityPolicy, systemSupport, windowManagerInternal, + mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerformer, awm); mUiAutomationServiceOwner = owner; mUiAutomationServiceInfo = accessibilityServiceInfo; @@ -239,11 +243,12 @@ class UiAutomationManager { UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, - SystemSupport systemSupport, WindowManagerInternal windowManagerInternal, + SystemSupport systemSupport, AccessibilityTrace trace, + WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm) { super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock, - securityPolicy, systemSupport, windowManagerInternal, systemActionPerformer, - awm); + securityPolicy, systemSupport, trace, windowManagerInternal, + systemActionPerformer, awm); mMainHandler = mainHandler; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java b/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java index feed18d438c7..3d8f5173d25a 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java @@ -98,8 +98,7 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene } mProcessMotionEvent = true; for (int i = 0; i < mGestureMatchers.size(); i++) { - final GestureMatcher matcher = - mGestureMatchers.get(i); + final GestureMatcher matcher = mGestureMatchers.get(i); matcher.onMotionEvent(event, rawEvent, policyFlags); if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) { clear(); @@ -128,7 +127,10 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene MotionEvent rawEvent, int policyFlags) { if (state == GestureMatcher.STATE_GESTURE_COMPLETED) { mListener.onGestureCompleted(gestureId, event, rawEvent, policyFlags); - //Clear the states in onMotionEvent(). + // Ideally we clear the states in onMotionEvent(), this case is for hold gestures. + // If we clear before processing up event , then MultiTap matcher cancels the gesture + // due to incorrect state. It ends up listener#onGestureCancelled is called even + // the gesture is detected. if (!mProcessMotionEvent) { clear(); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java index 7a4d9e34b657..570e0ce5490d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java @@ -33,7 +33,7 @@ import java.lang.annotation.RetentionPolicy; class MagnificationGestureMatcher { private static final int GESTURE_BASE = 100; - public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1; + public static final int GESTURE_TWO_FINGERS_DOWN_OR_SWIPE = GESTURE_BASE + 1; public static final int GESTURE_SWIPE = GESTURE_BASE + 2; public static final int GESTURE_SINGLE_TAP = GESTURE_BASE + 3; public static final int GESTURE_SINGLE_TAP_AND_HOLD = GESTURE_BASE + 4; @@ -41,7 +41,7 @@ class MagnificationGestureMatcher { public static final int GESTURE_TRIPLE_TAP_AND_HOLD = GESTURE_BASE + 6; @IntDef(prefix = {"GESTURE_MAGNIFICATION_"}, value = { - GESTURE_TWO_FINGER_DOWN, + GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, GESTURE_SWIPE }) @Retention(RetentionPolicy.SOURCE) @@ -57,8 +57,8 @@ class MagnificationGestureMatcher { switch (gestureId) { case GESTURE_SWIPE: return "GESTURE_SWIPE"; - case GESTURE_TWO_FINGER_DOWN: - return "GESTURE_TWO_FINGER_DOWN"; + case GESTURE_TWO_FINGERS_DOWN_OR_SWIPE: + return "GESTURE_TWO_FINGERS_DOWN_OR_SWIPE"; case GESTURE_SINGLE_TAP: return "GESTURE_SINGLE_TAP"; case GESTURE_SINGLE_TAP_AND_HOLD: @@ -71,6 +71,12 @@ class MagnificationGestureMatcher { return "none"; } + /** + * @param context + * @return the duration in milliseconds between the first tap's down event and + * the second tap's down event to be considered that the user is going to performing + * panning/scaling gesture. + */ static int getMagnificationMultiTapTimeout(Context context) { return ViewConfiguration.getDoubleTapTimeout() + context.getResources().getInteger( R.integer.config_screen_magnification_multi_tap_adjustment); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java index a209086ba475..085c343ff631 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java @@ -65,7 +65,7 @@ class MagnificationGesturesObserver implements GesturesObserver.Listener { * the last event before timeout. * * @see MagnificationGestureMatcher#GESTURE_SWIPE - * @see MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN + * @see MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE */ void onGestureCompleted(@GestureId int gestureId, long lastDownEventTime, List<MotionEventInfo> delayedEventQueue, MotionEvent event); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java b/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java index cd5061fa3163..fa15ac1c0e4f 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java @@ -49,6 +49,11 @@ class SimpleSwipe extends GestureMatcher { } @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelGesture(event, rawEvent, policyFlags); + } + + @Override protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (gestureMatched(event, rawEvent, policyFlags)) { completeGesture(event, rawEvent, policyFlags); @@ -65,7 +70,7 @@ class SimpleSwipe extends GestureMatcher { } private boolean gestureMatched(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - return mLastDown != null && (distance(mLastDown, event) >= mSwipeMinDistance); + return mLastDown != null && (distance(mLastDown, event) > mSwipeMinDistance); } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java deleted file mode 100644 index 173a5b82e003..000000000000 --- a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.accessibility.magnification; - -import android.content.Context; -import android.os.Handler; -import android.view.MotionEvent; - -import com.android.server.accessibility.gestures.GestureMatcher; - -/** - * - * This class is responsible for matching two fingers down gestures. The gesture matching - * result is determined in a duration. - */ -final class TwoFingersDown extends GestureMatcher { - - private MotionEvent mLastDown; - private final int mDetectionDurationMillis; - - TwoFingersDown(Context context) { - super(MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, - new Handler(context.getMainLooper()), null); - mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( - context); - } - - @Override - protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - mLastDown = MotionEvent.obtain(event); - cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags); - } - - @Override - protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (mLastDown == null) { - cancelGesture(event, rawEvent, policyFlags); - } - completeGesture(event, rawEvent, policyFlags); - } - - @Override - protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - cancelGesture(event, rawEvent, policyFlags); - } - - @Override - public void clear() { - if (mLastDown != null) { - mLastDown.recycle(); - mLastDown = null; - } - super.clear(); - } - - @Override - protected String getGestureName() { - return this.getClass().getSimpleName(); - } -} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java new file mode 100644 index 000000000000..1742bd46d865 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.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.accessibility.magnification; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Handler; +import android.util.MathUtils; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.server.accessibility.gestures.GestureMatcher; + +/** + * This class is responsible for detecting that the user is using two fingers to perform + * swiping gestures or just stay pressed on the screen. The gesture matching result is determined + * in a duration. + */ +final class TwoFingersDownOrSwipe extends GestureMatcher { + + private final int mDoubleTapTimeout; + private final int mDetectionDurationMillis; + private final int mSwipeMinDistance; + private MotionEvent mFirstPointerDown; + private MotionEvent mSecondPointerDown; + + TwoFingersDownOrSwipe(Context context) { + super(MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, + new Handler(context.getMainLooper()), null); + mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( + context); + mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout(); + mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop(); + + } + + @Override + protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + mFirstPointerDown = MotionEvent.obtain(event); + cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags); + } + + @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mFirstPointerDown == null) { + cancelGesture(event, rawEvent, policyFlags); + } + if (event.getPointerCount() == 2) { + mSecondPointerDown = MotionEvent.obtain(event); + completeAfter(mDoubleTapTimeout, event, rawEvent, policyFlags); + } else { + cancelGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mFirstPointerDown == null || mSecondPointerDown == null) { + return; + } + if (distance(mFirstPointerDown, /* move */ event) > mSwipeMinDistance) { + completeGesture(event, rawEvent, policyFlags); + return; + } + if (distance(mSecondPointerDown, /* move */ event) > mSwipeMinDistance) { + // The second pointer is swiping. + completeGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelGesture(event, rawEvent, policyFlags); + } + + @Override + protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelGesture(event, rawEvent, policyFlags); + } + + @Override + public void clear() { + if (mFirstPointerDown != null) { + mFirstPointerDown.recycle(); + mFirstPointerDown = null; + } + if (mSecondPointerDown != null) { + mSecondPointerDown.recycle(); + mSecondPointerDown = null; + } + super.clear(); + } + + @Override + protected String getGestureName() { + return this.getClass().getSimpleName(); + } + + private static double distance(@NonNull MotionEvent downEvent, @NonNull MotionEvent moveEvent) { + final int downActionIndex = downEvent.getActionIndex(); + final int downPointerId = downEvent.getPointerId(downActionIndex); + final int moveActionIndex = moveEvent.findPointerIndex(downPointerId); + if (moveActionIndex < 0) { + return -1; + } + return MathUtils.dist(downEvent.getX(downActionIndex), downEvent.getY(downActionIndex), + moveEvent.getX(moveActionIndex), moveEvent.getY(moveActionIndex)); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 55a911eea821..fa3406217fa8 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -323,7 +323,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl * manipulate the window magnifier or want to interact with current UI. The rule of leaving * this state is as follows: * <ol> - * <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN} is detected, + * <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE} is detected, * {@link State} will be transited to {@link PanningScalingGestureState}.</li> * <li> If other gesture is detected and the last motion event is neither ACTION_UP nor * ACTION_CANCEL. @@ -357,7 +357,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl new SimpleSwipe(context), multiTap, multiTapAndHold, - new TwoFingersDown(context)); + new TwoFingersDownOrSwipe(context)); } @Override @@ -399,7 +399,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl Slog.d(mLogTag, "onGestureDetected : delayedEventQueue = " + delayedEventQueue); } - if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN + if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE && mWindowMagnificationMgr.pointersInWindow(mDisplayId, motionEvent) > 0) { transitionTo(mObservePanningScalingState); } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) { diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 50ad6617b1fe..e251700498ee 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -19,6 +19,7 @@ package com.android.server.appwidget; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.res.Resources.ID_NULL; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; @@ -2578,9 +2579,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku info.updatePeriodMillis = sa.getInt( com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); info.initialLayout = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); + com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, ID_NULL); info.initialKeyguardLayout = sa.getResourceId(com.android.internal.R.styleable. - AppWidgetProviderInfo_initialKeyguardLayout, 0); + AppWidgetProviderInfo_initialKeyguardLayout, ID_NULL); String className = sa .getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure); @@ -2591,11 +2592,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku info.label = activityInfo.loadLabel(pm).toString(); info.icon = activityInfo.getIconResource(); info.previewImage = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); + com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, ID_NULL); info.previewLayout = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_previewLayout, 0); + com.android.internal.R.styleable.AppWidgetProviderInfo_previewLayout, ID_NULL); info.autoAdvanceViewId = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); + com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, + View.NO_ID); info.resizeMode = sa.getInt( com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, AppWidgetProviderInfo.RESIZE_NONE); @@ -2605,8 +2607,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku info.widgetFeatures = sa.getInt( com.android.internal.R.styleable.AppWidgetProviderInfo_widgetFeatures, 0); info.descriptionRes = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_description, - Resources.ID_NULL); + com.android.internal.R.styleable.AppWidgetProviderInfo_description, ID_NULL); sa.recycle(); return info; } catch (IOException | PackageManager.NameNotFoundException | XmlPullParserException e) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 21cae453d702..52237c934797 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -38,6 +38,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainRunna import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MINUTES; +import android.Manifest; import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; @@ -409,6 +410,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind checkCallerIsSystemOr(callingPackage); int userId = getCallingUserId(); checkUsesFeature(callingPackage, userId); + checkProfilePermissions(request); mFindDeviceCallback = callback; mRequest = request; @@ -519,6 +521,21 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } + private void checkProfilePermissions(AssociationRequest request) { + checkProfilePermission(request, + AssociationRequest.DEVICE_PROFILE_WATCH, + Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH); + } + + private void checkProfilePermission( + AssociationRequest request, String profile, String permission) { + if (profile.equals(request.getDeviceProfile()) + && getContext().checkCallingOrSelfPermission(permission) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Using " + profile + " requires " + permission); + } + } + @Override public PendingIntent requestNotificationAccess(ComponentName component) throws RemoteException { @@ -1330,7 +1347,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind mPermissionControllerManager.getPrivilegesDescriptionStringForProfile( deviceProfile, FgThread.getExecutor(), desc -> { try { - result.complete(desc); + result.complete(String.valueOf(desc)); } catch (Exception e) { result.completeExceptionally(e); } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 220f87db8bd4..f4a8ccd184e5 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -69,6 +69,7 @@ import android.provider.Settings; 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; @@ -81,6 +82,7 @@ import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IContentCaptureManager; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.view.contentcapture.IDataShareWriteAdapter; import com.android.internal.annotations.GuardedBy; @@ -88,7 +90,6 @@ import com.android.internal.infra.AbstractRemoteService; import com.android.internal.infra.GlobalWhitelistState; import com.android.internal.os.IResultReceiver; import com.android.internal.util.DumpUtils; -import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; @@ -101,6 +102,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -134,6 +136,9 @@ public final class ContentCaptureManagerService extends private final LocalService mLocalService = new LocalService(); + private final ContentCaptureManagerServiceStub mContentCaptureManagerServiceStub = + new ContentCaptureManagerServiceStub(); + @Nullable final LocalLog mRequestsHistory; @@ -224,8 +229,7 @@ public final class ContentCaptureManagerService extends @Override // from SystemService public void onStart() { - publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, - new ContentCaptureManagerServiceStub()); + publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, mContentCaptureManagerServiceStub); publishLocalService(ContentCaptureManagerInternal.class, mLocalService); } @@ -492,6 +496,19 @@ public final class ContentCaptureManagerService extends } } + 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); + } + } + } + } + private ActivityManagerInternal getAmInternal() { synchronized (mLock) { if (mAm == null) { @@ -599,13 +616,16 @@ public final class ContentCaptureManagerService extends } final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { + @GuardedBy("mLock") + private final ArrayMap<String, ArraySet<CallbackRecord>> mCallbacks = new ArrayMap<>(); @Override public void startSession(@NonNull IBinder activityToken, - @NonNull ComponentName componentName, int sessionId, int flags, - @NonNull IResultReceiver result) { - Preconditions.checkNotNull(activityToken); - Preconditions.checkNotNull(sessionId); + @NonNull IBinder shareableActivityToken, @NonNull ComponentName componentName, + int sessionId, int flags, @NonNull IResultReceiver result) { + Objects.requireNonNull(activityToken); + Objects.requireNonNull(shareableActivityToken); + Objects.requireNonNull(sessionId); final int userId = UserHandle.getCallingUserId(); final ActivityPresentationInfo activityPresentationInfo = getAmInternal() @@ -617,14 +637,14 @@ public final class ContentCaptureManagerService extends setClientState(result, STATE_DISABLED, /* binder= */ null); return; } - service.startSessionLocked(activityToken, activityPresentationInfo, sessionId, - Binder.getCallingUid(), flags, result); + service.startSessionLocked(activityToken, shareableActivityToken, + activityPresentationInfo, sessionId, Binder.getCallingUid(), flags, result); } } @Override public void finishSession(int sessionId) { - Preconditions.checkNotNull(sessionId); + Objects.requireNonNull(sessionId); final int userId = UserHandle.getCallingUserId(); synchronized (mLock) { @@ -650,7 +670,7 @@ public final class ContentCaptureManagerService extends @Override public void removeData(@NonNull DataRemovalRequest request) { - Preconditions.checkNotNull(request); + Objects.requireNonNull(request); assertCalledByPackageOwner(request.getPackageName()); final int userId = UserHandle.getCallingUserId(); @@ -663,8 +683,8 @@ public final class ContentCaptureManagerService extends @Override public void shareData(@NonNull DataShareRequest request, @NonNull IDataShareWriteAdapter clientAdapter) { - Preconditions.checkNotNull(request); - Preconditions.checkNotNull(clientAdapter); + Objects.requireNonNull(request); + Objects.requireNonNull(clientAdapter); assertCalledByPackageOwner(request.getPackageName()); @@ -754,6 +774,46 @@ public final class ContentCaptureManagerService extends } @Override + public void registerContentCaptureOptionsCallback(@NonNull String packageName, + 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); + } + + // 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); + } + } + record.unregisterObserver(); + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; @@ -1217,4 +1277,39 @@ public final class ContentCaptureManagerService extends 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/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index ea68e190ff1f..225a8d48114b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -35,6 +35,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; +import android.app.assist.ActivityId; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.content.ComponentName; @@ -241,6 +242,7 @@ final class ContentCapturePerUserService @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, + @NonNull IBinder shareableActivityToken, @NonNull ActivityPresentationInfo activityPresentationInfo, int sessionId, int uid, int flags, @NonNull IResultReceiver clientReceiver) { if (activityPresentationInfo == null) { @@ -340,8 +342,8 @@ final class ContentCapturePerUserService mRemoteService.ensureBoundLocked(); final ContentCaptureServerSession newSession = new ContentCaptureServerSession(mLock, - activityToken, this, componentName, clientReceiver, taskId, displayId, sessionId, - uid, flags); + activityToken, new ActivityId(taskId, shareableActivityToken), this, componentName, + clientReceiver, taskId, displayId, sessionId, uid, flags); if (mMaster.verbose) { Slog.v(TAG, "startSession(): new session for " + ComponentName.flattenToShortString(componentName) + " and id " + sessionId); @@ -595,9 +597,15 @@ final class ContentCapturePerUserService ? "null_activities" : activities.size() + " activities") + ")" + " for user " + mUserId); } + + ArraySet<String> oldList = + mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId); + mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); writeSetWhitelistEvent(getServiceComponentName(), packages, activities); + updateContentCaptureOptions(oldList); + // Must disable session that are not the allowlist anymore... final int numSessions = mSessions.size(); if (numSessions <= 0) return; @@ -669,5 +677,23 @@ final class ContentCapturePerUserService ContentCaptureMetricsLogger.writeSessionFlush(sessionId, getServiceComponentName(), app, flushMetrics, options, flushReason); } + + /** Updates {@link ContentCaptureOptions} for all newly added packages on allowlist. */ + private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) { + ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions + .getWhitelistedPackages(mUserId); + + if (oldList != null && adding != null) { + adding.removeAll(oldList); + } + + int N = adding != null ? adding.size() : 0; + for (int i = 0; i < N; i++) { + String packageName = adding.valueAt(i); + ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions + .getOptions(mUserId, packageName); + mMaster.updateOptions(packageName, options); + } + } } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 06ab426c6698..9f3045e68550 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -25,6 +25,7 @@ import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_RE import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_UPDATING; import android.annotation.NonNull; +import android.app.assist.ActivityId; import android.content.ComponentName; import android.os.Bundle; import android.os.IBinder; @@ -75,9 +76,9 @@ final class ContentCaptureServerSession { public final ComponentName appComponentName; ContentCaptureServerSession(@NonNull Object lock, @NonNull IBinder activityToken, - @NonNull ContentCapturePerUserService service, @NonNull ComponentName appComponentName, - @NonNull IResultReceiver sessionStateReceiver, int taskId, int displayId, int sessionId, - int uid, int flags) { + @NonNull ActivityId activityId, @NonNull ContentCapturePerUserService service, + @NonNull ComponentName appComponentName, @NonNull IResultReceiver sessionStateReceiver, + int taskId, int displayId, int sessionId, int uid, int flags) { Preconditions.checkArgument(sessionId != NO_SESSION_ID); mLock = lock; mActivityToken = activityToken; @@ -86,7 +87,7 @@ final class ContentCaptureServerSession { mId = sessionId; mUid = uid; mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null, - appComponentName, taskId, displayId, flags); + activityId, appComponentName, displayId, flags); mSessionStateReceiver = sessionStateReceiver; try { sessionStateReceiver.asBinder().linkToDeath(() -> onClientDeath(), 0); diff --git a/services/core/Android.bp b/services/core/Android.bp index b67bdc20f7fa..8ccfad6fe061 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -97,6 +97,7 @@ java_library_static { ":platform-compat-config", ":platform-compat-overrides", ":display-device-config", + ":display-layout-config", ":cec-config", ":device-state-config", "java/com/android/server/EventLogTags.logtags", @@ -222,7 +223,6 @@ filegroup { "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/DataConnectionStats.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/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java index fe3042d9da54..d83e2fd2090d 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/services/core/java/com/android/server/BootReceiver.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.system.OsConstants.O_RDONLY; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -24,12 +26,15 @@ import android.os.Build; import android.os.DropBoxManager; import android.os.Environment; import android.os.FileUtils; +import android.os.MessageQueue.OnFileDescriptorEventListener; import android.os.RecoverySystem; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.storage.StorageManager; import android.provider.Downloads; +import android.system.ErrnoException; +import android.system.Os; import android.text.TextUtils; import android.util.AtomicFile; import android.util.EventLog; @@ -46,11 +51,15 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedReader; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.regex.Matcher; @@ -116,6 +125,12 @@ public class BootReceiver extends BroadcastReceiver { private static final String METRIC_SYSTEM_SERVER = "shutdown_system_server"; private static final String METRIC_SHUTDOWN_TIME_START = "begin_shutdown"; + // Location of ftrace pipe for notifications from kernel memory tools like KFENCE and KASAN. + private static final String ERROR_REPORT_TRACE_PIPE = + "/sys/kernel/tracing/instances/bootreceiver/trace_pipe"; + // Avoid reporing the same bug from processDmesg() twice. + private static String sLastReportedBug = null; + @Override public void onReceive(final Context context, Intent intent) { // Log boot events in the background to avoid blocking the main thread with I/O @@ -143,6 +158,209 @@ public class BootReceiver extends BroadcastReceiver { } }.start(); + + FileDescriptor tracefd = null; + try { + tracefd = Os.open(ERROR_REPORT_TRACE_PIPE, O_RDONLY, 0600); + } catch (ErrnoException e) { + Slog.wtf(TAG, "Could not open " + ERROR_REPORT_TRACE_PIPE, e); + return; + } + + /* + * Event listener to watch for memory tool error reports. + * We read from /sys/kernel/tracing/instances/bootreceiver/trace_pipe (set up by the + * system), which will print an ftrace event when a memory corruption is detected in the + * kernel. + * When an error is detected, we run the dmesg shell command and process its output. + */ + OnFileDescriptorEventListener traceCallback = new OnFileDescriptorEventListener() { + final int mBufferSize = 1024; + byte[] mTraceBuffer = new byte[mBufferSize]; + @Override + public int onFileDescriptorEvents(FileDescriptor fd, int events) { + /* + * Read from the tracing pipe set up to monitor the error_report_end events. + * When a tracing event occurs, the kernel writes a short (~100 bytes) line to the + * pipe, e.g.: + * ...-11210 [004] d..1 285.322307: error_report_end: [kfence] ffffff8938a05000 + * The buffer size we use for reading should be enough to read the whole + * line, but to be on the safe side we keep reading until the buffer + * contains a '\n' character. In the unlikely case of a very buggy kernel + * the buffer may contain multiple tracing events that cannot be attributed + * to particular error reports. In that case the latest error report + * residing in dmesg is picked. + */ + try { + int nbytes = Os.read(fd, mTraceBuffer, 0, mBufferSize); + if (nbytes > 0) { + String readStr = new String(mTraceBuffer); + if (readStr.indexOf("\n") == -1) { + return OnFileDescriptorEventListener.EVENT_INPUT; + } + processDmesg(context); + } + } catch (Exception e) { + Slog.wtf(TAG, "Error processing dmesg output", e); + return 0; // Unregister the handler. + } + return OnFileDescriptorEventListener.EVENT_INPUT; + } + }; + + IoThread.get().getLooper().getQueue().addOnFileDescriptorEventListener( + tracefd, OnFileDescriptorEventListener.EVENT_INPUT, traceCallback); + + } + + /** + * Check whether it is safe to collect this dmesg line or not. + * + * We only consider lines belonging to KASAN or KFENCE reports, but those may still contain + * user information, namely the process name: + * + * [ 69.547684] [ T6006]c7 6006 CPU: 7 PID: 6006 Comm: sh Tainted: G S C O ... + * + * hardware information: + * + * [ 69.558923] [ T6006]c7 6006 Hardware name: <REDACTED> + * + * or register dump (in KASAN reports only): + * + * ... RIP: 0033:0x7f96443109da + * ... RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af + * ... RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da + * + * (on x86_64) + * + * ... pc : lpm_cpuidle_enter+0x258/0x384 + * ... lr : lpm_cpuidle_enter+0x1d4/0x384 + * ... sp : ffffff800820bea0 + * ... x29: ffffff800820bea0 x28: ffffffc2305f3ce0 + * ... ... + * ... x9 : 0000000000000001 x8 : 0000000000000000 + * (on ARM64) + * + * We therefore omit the lines that contain "Comm:", "Hardware name:", or match the general + * purpose register regexp. + * + * @param line single line of `dmesg` output. + * @return updated line with sensitive data removed, or null if the line must be skipped. + */ + public static String stripSensitiveData(String line) { + /* + * General purpose register names begin with "R" on x86_64 and "x" on ARM64. The letter is + * followed by two symbols (numbers, letters or spaces) and a colon, which is followed by a + * 16-digit hex number. The optional "_" prefix accounts for ORIG_RAX on x86. + */ + final String registerRegex = "[ _][Rx]..: [0-9a-f]{16}"; + final Pattern registerPattern = Pattern.compile(registerRegex); + + final String corruptionRegex = "Detected corrupted memory at 0x[0-9a-f]+"; + final Pattern corruptionPattern = Pattern.compile(corruptionRegex); + + if (line.contains("Comm: ") || line.contains("Hardware name: ")) return null; + if (registerPattern.matcher(line).find()) return null; + + Matcher cm = corruptionPattern.matcher(line); + if (cm.find()) return cm.group(0); + return line; + } + + /* + * Search dmesg output for the last error report from KFENCE or KASAN and copy it to Dropbox. + * + * Example report printed by the kernel (redacted to fit into 100 column limit): + * [ 69.236673] [ T6006]c7 6006 ========================================================= + * [ 69.245688] [ T6006]c7 6006 BUG: KFENCE: out-of-bounds in kfence_handle_page_fault + * [ 69.245688] [ T6006]c7 6006 + * [ 69.257816] [ T6006]c7 6006 Out-of-bounds access at 0xffffffca75c45000 (...) + * [ 69.267102] [ T6006]c7 6006 kfence_handle_page_fault+0x1bc/0x208 + * [ 69.273536] [ T6006]c7 6006 __do_kernel_fault+0xa8/0x11c + * ... + * [ 69.355427] [ T6006]c7 6006 kfence-#2 [0xffffffca75c46f30-0xffffffca75c46fff, ... + * [ 69.366938] [ T6006]c7 6006 __d_alloc+0x3c/0x1b4 + * [ 69.371946] [ T6006]c7 6006 d_alloc_parallel+0x48/0x538 + * [ 69.377578] [ T6006]c7 6006 __lookup_slow+0x60/0x15c + * ... + * [ 69.547684] [ T6006]c7 6006 CPU: 7 PID: 6006 Comm: sh Tainted: G S C O ... + * [ 69.558923] [ T6006]c7 6006 Hardware name: <REDACTED> + * [ 69.567059] [ T6006]c7 6006 ========================================================= + * + * We rely on the kernel printing task/CPU ID for every log line (CONFIG_PRINTK_CALLER=y). + * E.g. for the above report the task ID is T6006. Report lines may interleave with lines + * printed by other kernel tasks, which will have different task IDs, so in order to collect + * the report we: + * - find the next occurrence of the "BUG: " line in the kernel log, parse it to obtain the + * task ID and the tool name; + * - scan the rest of dmesg output and pick every line that has the same task ID, until we + * encounter a horizontal ruler, i.e.: + * [ 69.567059] [ T6006]c7 6006 ====================================================== + * - add that line to the error report, unless it contains sensitive information (see + * logLinePotentiallySensitive()) + * - repeat the above steps till the last report is found. + */ + private void processDmesg(Context ctx) throws IOException { + + /* + * Only SYSTEM_KASAN_ERROR_REPORT and SYSTEM_KFENCE_ERROR_REPORT are supported at the + * moment. + */ + final String[] bugTypes = new String[] { "KASAN", "KFENCE" }; + final String tsRegex = "^\\[[^]]+\\] "; + final String bugRegex = + tsRegex + "\\[([^]]+)\\].*BUG: (" + String.join("|", bugTypes) + "):"; + final Pattern bugPattern = Pattern.compile(bugRegex); + + Process p = new ProcessBuilder("/system/bin/timeout", "-k", "90s", "60s", + "dmesg").redirectErrorStream(true).start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = null; + String task = null; + String tool = null; + String bugTitle = null; + Pattern reportPattern = null; + ArrayList<String> currentReport = null; + String lastReport = null; + + while ((line = reader.readLine()) != null) { + if (currentReport == null) { + Matcher bm = bugPattern.matcher(line); + if (!bm.find()) continue; + task = bm.group(1); + tool = bm.group(2); + bugTitle = line; + currentReport = new ArrayList<String>(); + currentReport.add(line); + String reportRegex = tsRegex + "\\[" + task + "\\].*"; + reportPattern = Pattern.compile(reportRegex); + continue; + } + Matcher rm = reportPattern.matcher(line); + if (!rm.matches()) continue; + if ((line = stripSensitiveData(line)) == null) continue; + if (line.contains("================================")) { + lastReport = String.join("\n", currentReport); + currentReport = null; + continue; + } + currentReport.add(line); + } + if (lastReport == null) { + Slog.w(TAG, "Could not find report in dmesg."); + return; + } + + // Avoid sending the same bug report twice. + if (bugTitle == sLastReportedBug) return; + + final String reportTag = "SYSTEM_" + tool + "_ERROR_REPORT"; + final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); + final String headers = getCurrentBootHeaders(); + final String reportText = headers + lastReport; + + addTextToDropBox(db, reportTag, reportText, "/dev/kmsg", LOG_SIZE); + sLastReportedBug = bugTitle; } private void removeOldUpdatePackages(Context context) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 542d527177a1..d7559873025d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -70,6 +70,7 @@ import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.PendingIntent; +import android.app.usage.NetworkStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -95,7 +96,6 @@ import android.net.INetworkActivityListener; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.INetworkStatsService; import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; @@ -120,6 +120,7 @@ import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkTestResultParcelable; import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; @@ -141,10 +142,13 @@ import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; import android.net.VpnTransportInfo; -import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; +import android.net.resolv.aidl.DnsHealthEventParcel; +import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener; +import android.net.resolv.aidl.Nat64PrefixEventParcel; +import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; @@ -155,7 +159,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.Messenger; @@ -187,7 +190,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; -import com.android.internal.logging.MetricsLogger; import com.android.internal.util.AsyncChannel; import com.android.internal.util.BitUtils; import com.android.internal.util.IndentingPrintWriter; @@ -201,7 +203,6 @@ import com.android.net.module.util.LinkPropertiesUtils.CompareResult; import com.android.net.module.util.PermissionUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.AutodestructReference; -import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; import com.android.server.connectivity.KeepaliveTracker; @@ -324,12 +325,11 @@ public class ConnectivityService extends IConnectivityManager.Stub // 0 is full bad, 100 is full good private int mDefaultInetConditionPublished = 0; - private INetworkManagementService mNMS; @VisibleForTesting protected IDnsResolver mDnsResolver; @VisibleForTesting protected INetd mNetd; - private INetworkStatsService mStatsService; + private NetworkStatsManager mStatsManager; private NetworkPolicyManager mPolicyManager; private NetworkPolicyManagerInternal mPolicyManagerInternal; private final NetdCallback mNetdCallback; @@ -1040,16 +1040,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - public ConnectivityService(Context context, INetworkManagementService netManager, - INetworkStatsService statsService) { - this(context, netManager, statsService, getDnsResolver(context), new IpConnectivityLog(), + public ConnectivityService(Context context) { + this(context, getDnsResolver(context), new IpConnectivityLog(), NetdService.getInstance(), new Dependencies()); } @VisibleForTesting - protected ConnectivityService(Context context, INetworkManagementService netManager, - INetworkStatsService statsService, IDnsResolver dnsresolver, IpConnectivityLog logger, - INetd netd, Dependencies deps) { + protected ConnectivityService(Context context, IDnsResolver dnsresolver, + IpConnectivityLog logger, INetd netd, Dependencies deps) { if (DBG) log("ConnectivityService starting up"); mDeps = Objects.requireNonNull(deps, "missing Dependencies"); @@ -1095,8 +1093,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Consider making the timer customizable. mNascentDelayMs = DEFAULT_NASCENT_DELAY_MS; - mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService"); - mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService"); + mStatsManager = mContext.getSystemService(NetworkStatsManager.class); mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class); mPolicyManagerInternal = Objects.requireNonNull( LocalServices.getService(NetworkPolicyManagerInternal.class), @@ -1203,7 +1200,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mUserAllContext.registerReceiver(mIntentReceiver, intentFilter, null /* broadcastPermission */, mHandler); - mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNMS, mNetd); + mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNetd); mNetdCallback = new NetdCallback(); try { @@ -1215,9 +1212,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mSettingsObserver = new SettingsObserver(mContext, mHandler); registerSettingsCallbacks(); - final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler); - dataConnectionStats.startMonitoring(); - mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler); mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager); mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter); @@ -1237,12 +1231,12 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager = new DnsManager(mContext, mDnsResolver); registerPrivateDnsSettingsCallbacks(); - mNoServiceNetwork = new NetworkAgentInfo(null, + 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, null, 0, INVALID_UID, + null, 0, INVALID_UID, mQosCallbackTracker); } @@ -1403,7 +1397,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } - private NetworkState getUnfilteredActiveNetworkState(int uid) { + private NetworkAgentInfo getNetworkAgentInfoForUid(int uid) { NetworkAgentInfo nai = getDefaultNetworkForUid(uid); final Network[] networks = getVpnUnderlyingNetworks(uid); @@ -1419,12 +1413,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai = null; } } - - if (nai != null) { - return nai.getNetworkState(); - } else { - return NetworkState.EMPTY; - } + return nai; } /** @@ -1477,24 +1466,31 @@ public class ConnectivityService extends IConnectivityManager.Stub "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId())); } - private void filterNetworkInfo(@NonNull NetworkInfo networkInfo, - @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) { - if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) { - networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); - } - networkInfo.setDetailedState( - getLegacyLockdownState(networkInfo.getDetailedState()), - "" /* reason */, null /* extraInfo */); - } - /** - * Apply any relevant filters to {@link NetworkState} for the given UID. For + * Apply any relevant filters to the specified {@link NetworkInfo} for the given UID. For * example, this may mark the network as {@link DetailedState#BLOCKED} based * on {@link #isNetworkWithCapabilitiesBlocked}. */ - private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) { - if (state == null || state.networkInfo == null || state.linkProperties == null) return; - filterNetworkInfo(state.networkInfo, state.networkCapabilities, uid, ignoreBlocked); + @NonNull + private NetworkInfo filterNetworkInfo(@NonNull NetworkInfo networkInfo, int type, + @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) { + final NetworkInfo filtered = new NetworkInfo(networkInfo); + // Many legacy types (e.g,. TYPE_MOBILE_HIPRI) are not actually a property of the network + // but only exists if an app asks about them or requests them. Ensure the requesting app + // gets the type it asks for. + filtered.setType(type); + final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked) + ? DetailedState.BLOCKED + : filtered.getDetailedState(); + filtered.setDetailedState(getLegacyLockdownState(state), + "" /* reason */, null /* extraInfo */); + return filtered; + } + + private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid, + boolean ignoreBlocked) { + return filterNetworkInfo(nai.networkInfo, nai.networkInfo.getType(), + nai.networkCapabilities, uid, ignoreBlocked); } /** @@ -1508,10 +1504,11 @@ public class ConnectivityService extends IConnectivityManager.Stub public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); final int uid = mDeps.getCallingUid(); - final NetworkState state = getUnfilteredActiveNetworkState(uid); - filterNetworkStateForUid(state, uid, false); - maybeLogBlockedNetworkInfo(state.networkInfo, uid); - return state.networkInfo; + final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false); + maybeLogBlockedNetworkInfo(networkInfo, uid); + return networkInfo; } @Override @@ -1546,30 +1543,37 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { PermissionUtils.enforceNetworkStackPermission(mContext); - final NetworkState state = getUnfilteredActiveNetworkState(uid); - filterNetworkStateForUid(state, uid, ignoreBlocked); - return state.networkInfo; + final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + return getFilteredNetworkInfo(nai, uid, ignoreBlocked); + } + + /** Returns a NetworkInfo object for a network that doesn't exist. */ + private NetworkInfo makeFakeNetworkInfo(int networkType, int uid) { + final NetworkInfo info = new NetworkInfo(networkType, 0 /* subtype */, + getNetworkTypeName(networkType), "" /* subtypeName */); + info.setIsAvailable(true); + // For compatibility with legacy code, return BLOCKED instead of DISCONNECTED when + // background data is restricted. + final NetworkCapabilities nc = new NetworkCapabilities(); // Metered. + final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false) + ? DetailedState.BLOCKED + : DetailedState.DISCONNECTED; + info.setDetailedState(getLegacyLockdownState(state), + "" /* reason */, null /* extraInfo */); + return info; } - private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) { + private NetworkInfo getFilteredNetworkInfoForType(int networkType, int uid) { if (!mLegacyTypeTracker.isTypeSupported(networkType)) { return null; } final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - final NetworkInfo info; - final NetworkCapabilities nc; - if (nai != null) { - info = new NetworkInfo(nai.networkInfo); - info.setType(networkType); - nc = nai.networkCapabilities; - } else { - info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), ""); - info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null); - info.setIsAvailable(true); - nc = new NetworkCapabilities(); + if (nai == null) { + return makeFakeNetworkInfo(networkType, uid); } - filterNetworkInfo(info, nc, uid, false); - return info; + return filterNetworkInfo(nai.networkInfo, networkType, nai.networkCapabilities, uid, + false); } @Override @@ -1579,27 +1583,23 @@ public class ConnectivityService extends IConnectivityManager.Stub if (getVpnUnderlyingNetworks(uid) != null) { // A VPN is active, so we may need to return one of its underlying networks. This // information is not available in LegacyTypeTracker, so we have to get it from - // getUnfilteredActiveNetworkState. - final NetworkState state = getUnfilteredActiveNetworkState(uid); - if (state.networkInfo != null && state.networkInfo.getType() == networkType) { - filterNetworkStateForUid(state, uid, false); - return state.networkInfo; + // getNetworkAgentInfoForUid. + final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false); + if (networkInfo.getType() == networkType) { + return networkInfo; } } - return getFilteredNetworkInfo(networkType, uid); + return getFilteredNetworkInfoForType(networkType, uid); } @Override public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) { enforceAccessPermission(); final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - if (nai != null) { - final NetworkState state = nai.getNetworkState(); - filterNetworkStateForUid(state, uid, ignoreBlocked); - return state.networkInfo; - } else { - return null; - } + if (nai == null) return null; + return getFilteredNetworkInfo(nai, uid, ignoreBlocked); } @Override @@ -1627,10 +1627,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } final int uid = mDeps.getCallingUid(); - if (!isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) { - return nai.network; + if (isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) { + return null; } - return null; + return nai.network; } @Override @@ -1719,9 +1719,9 @@ public class ConnectivityService extends IConnectivityManager.Stub public LinkProperties getActiveLinkProperties() { enforceAccessPermission(); final int uid = mDeps.getCallingUid(); - NetworkState state = getUnfilteredActiveNetworkState(uid); - if (state.linkProperties == null) return null; - return linkPropertiesRestrictedForCallerPermissions(state.linkProperties, + NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + return linkPropertiesRestrictedForCallerPermissions(nai.linkProperties, Binder.getCallingPid(), uid); } @@ -1886,24 +1886,46 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // TODO: Consider delete this function or turn it into a no-op method. @Override public NetworkState[] getAllNetworkState() { // This contains IMSI details, so make sure the caller is privileged. PermissionUtils.enforceNetworkStackPermission(mContext); final ArrayList<NetworkState> result = new ArrayList<>(); + for (NetworkStateSnapshot snapshot : getAllNetworkStateSnapshot()) { + // NetworkStateSnapshot doesn't contain NetworkInfo, so need to fetch it from the + // NetworkAgentInfo. + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(snapshot.network); + if (nai != null && nai.networkInfo.isConnected()) { + result.add(new NetworkState(new NetworkInfo(nai.networkInfo), + snapshot.linkProperties, snapshot.networkCapabilities, snapshot.network, + snapshot.subscriberId)); + } + } + return result.toArray(new NetworkState[result.size()]); + } + + @Override + @NonNull + public List<NetworkStateSnapshot> getAllNetworkStateSnapshot() { + // This contains IMSI details, so make sure the caller is privileged. + PermissionUtils.enforceNetworkStackPermission(mContext); + + final ArrayList<NetworkStateSnapshot> result = new ArrayList<>(); for (Network network : getAllNetworks()) { final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - // TODO: Consider include SUSPENDED networks. + // TODO: Consider include SUSPENDED networks, which should be considered as + // temporary shortage of connectivity of a connected network. if (nai != null && nai.networkInfo.isConnected()) { - // TODO (b/73321673) : NetworkState contains a copy of the + // TODO (b/73321673) : NetworkStateSnapshot contains a copy of the // NetworkCapabilities, which may contain UIDs of apps to which the // network applies. Should the UIDs be cleared so as not to leak or // interfere ? - result.add(nai.getNetworkState()); + result.add(nai.getNetworkStateSnapshot()); } } - return result.toArray(new NetworkState[result.size()]); + return result; } @Override @@ -2040,25 +2062,24 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - private class NetdEventCallback extends INetdEventListener.Stub { + class DnsResolverUnsolicitedEventCallback extends + IDnsResolverUnsolicitedEventListener.Stub { @Override - public void onPrivateDnsValidationEvent(int netId, String ipAddress, - String hostname, boolean validated) { + public void onPrivateDnsValidationEvent(final PrivateDnsValidationEventParcel event) { try { mHandler.sendMessage(mHandler.obtainMessage( EVENT_PRIVATE_DNS_VALIDATION_UPDATE, - new PrivateDnsValidationUpdate(netId, - InetAddresses.parseNumericAddress(ipAddress), - hostname, validated))); + new PrivateDnsValidationUpdate(event.netId, + InetAddresses.parseNumericAddress(event.ipAddress), + event.hostname, event.validation))); } catch (IllegalArgumentException e) { loge("Error parsing ip address in validation event"); } } @Override - public void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, - String hostname, String[] ipAddresses, int ipAddressesCount, int uid) { - NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId); + public void onDnsHealthEvent(final DnsHealthEventParcel event) { + NetworkAgentInfo nai = getNetworkAgentInfoForNetId(event.netId); // Netd event only allow registrants from system. Each NetworkMonitor thread is under // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd // event callback for certain nai. e.g. cellular. Register here to pass to @@ -2067,34 +2088,18 @@ public class ConnectivityService extends IConnectivityManager.Stub // callback from each caller type. Need to re-factor NetdEventListenerService to allow // multiple NetworkMonitor registrants. if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) { - nai.networkMonitor().notifyDnsResponse(returnCode); + nai.networkMonitor().notifyDnsResponse(event.healthResult); } } @Override - public void onNat64PrefixEvent(int netId, boolean added, - String prefixString, int prefixLength) { - mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength)); - } - - @Override - public void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, - int uid) { + public void onNat64PrefixEvent(final Nat64PrefixEventParcel event) { + mHandler.post(() -> handleNat64PrefixEvent(event.netId, event.prefixOperation, + event.prefixAddress, event.prefixLength)); } @Override - public void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, - byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, - long timestampNs) { - } - - @Override - public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, - int[] rttsUs, int[] sentAckDiffsMs) { - } - - @Override - public int getInterfaceVersion() throws RemoteException { + public int getInterfaceVersion() { return this.VERSION; } @@ -2102,16 +2107,17 @@ public class ConnectivityService extends IConnectivityManager.Stub public String getInterfaceHash() { return this.HASH; } - }; + } @VisibleForTesting - protected final INetdEventListener mNetdEventCallback = new NetdEventCallback(); + protected final DnsResolverUnsolicitedEventCallback mResolverUnsolEventCallback = + new DnsResolverUnsolicitedEventCallback(); - private void registerNetdEventCallback() { + private void registerDnsResolverUnsolicitedEventListener() { try { - mDnsResolver.registerEventListener(mNetdEventCallback); + mDnsResolver.registerUnsolicitedEventListener(mResolverUnsolEventCallback); } catch (Exception e) { - loge("Error registering DnsResolver callback: " + e); + loge("Error registering DnsResolver unsolicited event callback: " + e); } } @@ -2201,8 +2207,45 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + /** + * Performs a strict and comprehensive check of whether a calling package is allowed to + * change the state of network, as the condition differs for pre-M, M+, and + * privileged/preinstalled apps. The caller is expected to have either the + * CHANGE_NETWORK_STATE or the WRITE_SETTINGS permission declared. Either of these + * permissions allow changing network state; WRITE_SETTINGS is a runtime permission and + * can be revoked, but (except in M, excluding M MRs), CHANGE_NETWORK_STATE is a normal + * permission and cannot be revoked. See http://b/23597341 + * + * Note: if the check succeeds because the application holds WRITE_SETTINGS, the operation + * of this app will be updated to the current time. + */ private void enforceChangePermission(String callingPkg, String callingAttributionTag) { - ConnectivityManager.enforceChangePermission(mContext, callingPkg, callingAttributionTag); + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE) + == PackageManager.PERMISSION_GRANTED) { + return; + } + + if (callingPkg == null) { + throw new SecurityException("Calling package name is null."); + } + + final AppOpsManager appOpsMgr = mContext.getSystemService(AppOpsManager.class); + final int uid = mDeps.getCallingUid(); + final int mode = appOpsMgr.noteOpNoThrow(AppOpsManager.OPSTR_WRITE_SETTINGS, uid, + callingPkg, callingAttributionTag, null /* message */); + + if (mode == AppOpsManager.MODE_ALLOWED) { + return; + } + + if ((mode == AppOpsManager.MODE_DEFAULT) && (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SETTINGS) == PackageManager.PERMISSION_GRANTED)) { + return; + } + + throw new SecurityException(callingPkg + " was not granted either of these permissions:" + + android.Manifest.permission.CHANGE_NETWORK_STATE + "," + + android.Manifest.permission.WRITE_SETTINGS + "."); } private void enforceSettingsPermission() { @@ -2363,13 +2406,6 @@ public class ConnectivityService extends IConnectivityManager.Stub final BroadcastOptions opts = BroadcastOptions.makeBasic(); opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M); options = opts.toBundle(); - final IBatteryStats bs = mDeps.getBatteryStatsService(); - try { - bs.noteConnectivityChanged(intent.getIntExtra( - ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE), - ni.getState().toString()); - } catch (RemoteException e) { - } intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); } try { @@ -2405,7 +2441,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // to ensure the tracking will be initialized correctly. mPermissionMonitor.startMonitoring(); mProxyTracker.loadGlobalProxy(); - registerNetdEventCallback(); + registerDnsResolverUnsolicitedEventListener(); synchronized (this) { mSystemReady = true; @@ -3046,9 +3082,6 @@ public class ConnectivityService extends IConnectivityManager.Stub log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg); } if (valid != nai.lastValidated) { - if (wasDefault) { - mMetricsLog.logDefaultNetworkValidity(valid); - } final int oldScore = nai.getCurrentScore(); nai.lastValidated = valid; nai.everValidated |= valid; @@ -3172,16 +3205,16 @@ public class ConnectivityService extends IConnectivityManager.Stub // Invoke ConnectivityReport generation for this Network test event. final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(mNetId); if (nai == null) return; - final Message m = mConnectivityDiagnosticsHandler.obtainMessage( - ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, - new ConnectivityReportEvent(p.timestampMillis, nai)); final PersistableBundle extras = new PersistableBundle(); extras.putInt(KEY_NETWORK_VALIDATION_RESULT, p.result); extras.putInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, p.probesSucceeded); extras.putInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK, p.probesAttempted); - m.setData(new Bundle(extras)); + ConnectivityReportEvent reportEvent = + new ConnectivityReportEvent(p.timestampMillis, nai, extras); + final Message m = mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, reportEvent); mConnectivityDiagnosticsHandler.sendMessage(m); } @@ -3268,8 +3301,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final Message msg = mConnectivityDiagnosticsHandler.obtainMessage( ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED, detectionMethod, netId, - p.timestampMillis); - msg.setData(new Bundle(extras)); + new Pair<>(p.timestampMillis, extras)); // NetworkStateTrackerHandler currently doesn't take any actions based on data // stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid @@ -3336,21 +3368,21 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } - private void handleNat64PrefixEvent(int netId, boolean added, String prefixString, + private void handleNat64PrefixEvent(int netId, int operation, String prefixAddress, int prefixLength) { NetworkAgentInfo nai = mNetworkForNetId.get(netId); if (nai == null) return; - log(String.format("NAT64 prefix %s on netId %d: %s/%d", - (added ? "added" : "removed"), netId, prefixString, prefixLength)); + log(String.format("NAT64 prefix changed on netId %d: operation=%d, %s/%d", + netId, operation, prefixAddress, prefixLength)); IpPrefix prefix = null; - if (added) { + if (operation == IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED) { try { - prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixString), + prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixAddress), prefixLength); } catch (IllegalArgumentException e) { - loge("Invalid NAT64 prefix " + prefixString + "/" + prefixLength); + loge("Invalid NAT64 prefix " + prefixAddress + "/" + prefixLength); return; } } @@ -3469,14 +3501,6 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean wasDefault = isDefaultNetwork(nai); if (wasDefault) { mDefaultInetConditionPublished = 0; - // Log default network disconnection before required book-keeping. - // Let rematchAllNetworksAndRequests() below record a new default network event - // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence - // whose timestamps tell how long it takes to recover a default network. - long now = SystemClock.elapsedRealtime(); - mMetricsLog.logDefaultNetworkEvent(null, 0, false, - null /* lp */, null /* nc */, nai.network, nai.getCurrentScore(), - nai.linkProperties, nai.networkCapabilities); } notifyIfacesChangedForNetworkStats(); // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied @@ -3818,7 +3842,24 @@ public class ConnectivityService extends IConnectivityManager.Stub removeListenRequestFromNetworks(req); } } - mDefaultNetworkRequests.remove(nri); + if (mDefaultNetworkRequests.remove(nri)) { + // If this request was one of the defaults, then the UID rules need to be updated + // WARNING : if the app(s) for which this network request is the default are doing + // traffic, this will kill their connected sockets, even if an equivalent request + // is going to be reinstated right away ; unconnected traffic will go on the default + // until the new default is set, which will happen very soon. + // TODO : The only way out of this is to diff old defaults and new defaults, and only + // remove ranges for those requests that won't have a replacement + final NetworkAgentInfo satisfier = nri.getSatisfier(); + if (null != satisfier) { + try { + mNetd.networkRemoveUidRanges(satisfier.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } catch (RemoteException e) { + loge("Exception setting network preference default network", e); + } + } + } mNetworkRequestCounter.decrementCount(nri.mUid); mNetworkRequestInfoLogs.log("RELEASE " + nri); @@ -4131,13 +4172,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // nai.networkMonitor() is thread-safe return nai.networkMonitor(); } - - @Override - public void logEvent(int eventId, String packageName) { - enforceSettingsPermission(); - - new MetricsLogger().action(eventId, packageName); - } } public boolean avoidBadWifi() { @@ -4467,16 +4501,13 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_SET_REQUIRE_VPN_FOR_UIDS: handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); break; - case EVENT_SET_OEM_NETWORK_PREFERENCE: + case EVENT_SET_OEM_NETWORK_PREFERENCE: { final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg = (Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener>) msg.obj; - try { - handleSetOemNetworkPreference(arg.first, arg.second); - } catch (RemoteException e) { - loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e); - } + handleSetOemNetworkPreference(arg.first, arg.second); break; + } case EVENT_REPORT_NETWORK_ACTIVITY: mNetworkActivityTracker.handleReportNetworkActivity(); break; @@ -5018,10 +5049,16 @@ public class ConnectivityService extends IConnectivityManager.Stub private void onUserAdded(UserHandle user) { mPermissionMonitor.onUserAdded(user); + if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) { + handleSetOemNetworkPreference(mOemNetworkPreferences, null); + } } private void onUserRemoved(UserHandle user) { mPermissionMonitor.onUserRemoved(user); + if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) { + handleSetOemNetworkPreference(mOemNetworkPreferences, null); + } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -5234,11 +5271,20 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureAllNetworkRequestsHaveType(r); mRequests = initializeRequests(r); mNetworkRequestForCallback = nri.getNetworkRequestForCallback(); + // Note here that the satisfier may have corresponded to an old request, that + // this code doesn't try to take over. While it is a small discrepancy in the + // structure of these requests, it will be fixed by the next rematch and it's + // not as bad as having an NRI not storing its real satisfier. + // Fixing this discrepancy would require figuring out in the copying code what + // is the new request satisfied by this, which is a bit complex and not very + // useful as no code is using it until rematch fixes it. + mSatisfier = nri.mSatisfier; mMessenger = nri.mMessenger; mBinder = nri.mBinder; mPid = nri.mPid; mUid = nri.mUid; mPendingIntent = nri.mPendingIntent; + mNetworkRequestCounter.incrementCountOrThrow(mUid); mCallingAttributionTag = nri.mCallingAttributionTag; } @@ -5285,6 +5331,8 @@ public class ConnectivityService extends IConnectivityManager.Stub public String toString() { return "uid/pid:" + mUid + "/" + mPid + " active request Id: " + (mActiveRequest == null ? null : mActiveRequest.requestId) + + " callback request Id: " + + mNetworkRequestForCallback.requestId + " " + mRequests + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent); } @@ -6030,7 +6078,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), - this, mNetd, mDnsResolver, mNMS, providerId, uid, mQosCallbackTracker); + this, mNetd, mDnsResolver, providerId, uid, mQosCallbackTracker); // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says. processCapabilitiesFromAgent(nai, nc); @@ -7112,27 +7160,6 @@ public class ConnectivityService extends IConnectivityManager.Stub updateTcpBufferSizes(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null); notifyIfacesChangedForNetworkStats(); - - // Log 0 -> X and Y -> X default network transitions, where X is the new default. - final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null; - final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0; - final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated; - final LinkProperties lp = (newDefaultNetwork != null) - ? newDefaultNetwork.linkProperties : null; - final NetworkCapabilities nc = (newDefaultNetwork != null) - ? newDefaultNetwork.networkCapabilities : null; - - final Network prevNetwork = (oldDefaultNetwork != null) - ? oldDefaultNetwork.network : null; - final int prevScore = (oldDefaultNetwork != null) - ? oldDefaultNetwork.getCurrentScore() : 0; - final LinkProperties prevLp = (oldDefaultNetwork != null) - ? oldDefaultNetwork.linkProperties : null; - final NetworkCapabilities prevNc = (oldDefaultNetwork != null) - ? oldDefaultNetwork.networkCapabilities : null; - - mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc, - prevNetwork, prevScore, prevLp, prevNc); } private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri, @@ -7162,7 +7189,7 @@ public class ConnectivityService extends IConnectivityManager.Stub toUidRangeStableParcels(nri.getUids())); } } catch (RemoteException | ServiceSpecificException e) { - loge("Exception setting OEM network preference default network :" + e); + loge("Exception setting app default network", e); } } @@ -7217,13 +7244,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private static class NetworkReassignment { static class RequestReassignment { @NonNull public final NetworkRequestInfo mNetworkRequestInfo; - @NonNull public final NetworkRequest mOldNetworkRequest; - @NonNull public final NetworkRequest mNewNetworkRequest; + @Nullable public final NetworkRequest mOldNetworkRequest; + @Nullable public final NetworkRequest mNewNetworkRequest; @Nullable public final NetworkAgentInfo mOldNetwork; @Nullable public final NetworkAgentInfo mNewNetwork; RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo, - @NonNull final NetworkRequest oldNetworkRequest, - @NonNull final NetworkRequest newNetworkRequest, + @Nullable final NetworkRequest oldNetworkRequest, + @Nullable final NetworkRequest newNetworkRequest, @Nullable final NetworkAgentInfo oldNetwork, @Nullable final NetworkAgentInfo newNetwork) { mNetworkRequestInfo = networkRequestInfo; @@ -7234,7 +7261,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } public String toString() { - return mNetworkRequestInfo.mRequests.get(0).requestId + " : " + final NetworkRequest requestToShow = null != mNewNetworkRequest + ? mNewNetworkRequest : mNetworkRequestInfo.mRequests.get(0); + return requestToShow.requestId + " : " + (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null") + " → " + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null"); } @@ -7247,7 +7276,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } void addRequestReassignment(@NonNull final RequestReassignment reassignment) { - if (!Build.IS_USER) { + if (Build.IS_DEBUGGABLE) { // The code is never supposed to add two reassignments of the same request. Make // sure this stays true, but without imposing this expensive check on all // reassignments on all user devices. @@ -7294,14 +7323,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri, - @NonNull final NetworkRequest previousRequest, - @NonNull final NetworkRequest newRequest, + @Nullable final NetworkRequest previousRequest, + @Nullable final NetworkRequest newRequest, @Nullable final NetworkAgentInfo previousSatisfier, @Nullable final NetworkAgentInfo newSatisfier, final long now) { if (null != newSatisfier && mNoServiceNetwork != newSatisfier) { if (VDBG) log("rematch for " + newSatisfier.toShortString()); - if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) { + if (null != previousRequest && null != previousSatisfier) { if (VDBG || DDBG) { log(" accepting network in place of " + previousSatisfier.toShortString()); } @@ -7318,12 +7347,13 @@ public class ConnectivityService extends IConnectivityManager.Stub newSatisfier.unlingerRequest(NetworkRequest.REQUEST_ID_NONE); } + // if newSatisfier is not null, then newRequest may not be null. newSatisfier.unlingerRequest(newRequest.requestId); if (!newSatisfier.addRequest(newRequest)) { Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has " + newRequest); } - } else if (null != previousSatisfier) { + } else if (null != previousRequest && null != previousSatisfier) { if (DBG) { log("Network " + previousSatisfier.toShortString() + " stopped satisfying" + " request " + previousRequest.requestId); @@ -7921,7 +7951,8 @@ public class ConnectivityService extends IConnectivityManager.Stub * * Must be called on the handler thread. */ - private Network[] getDefaultNetworks() { + @NonNull + private ArrayList<Network> getDefaultNetworks() { ensureRunningOnConnectivityServiceThread(); final ArrayList<Network> defaultNetworks = new ArrayList<>(); final Set<Integer> activeNetIds = new ArraySet<>(); @@ -7935,7 +7966,7 @@ public class ConnectivityService extends IConnectivityManager.Stub defaultNetworks.add(nai.network); } } - return defaultNetworks.toArray(new Network[0]); + return defaultNetworks; } /** @@ -7952,8 +7983,16 @@ public class ConnectivityService extends IConnectivityManager.Stub final UnderlyingNetworkInfo[] underlyingNetworkInfos = getAllVpnInfo(); try { - mStatsService.forceUpdateIfaces(getDefaultNetworks(), getAllNetworkState(), activeIface, - underlyingNetworkInfos); + final ArrayList<NetworkStateSnapshot> snapshots = new ArrayList<>(); + // TODO: Directly use NetworkStateSnapshot when feasible. + for (final NetworkState state : getAllNetworkState()) { + final NetworkStateSnapshot snapshot = new NetworkStateSnapshot(state.network, + state.networkCapabilities, state.linkProperties, state.subscriberId, + state.legacyNetworkType); + snapshots.add(snapshot); + } + mStatsManager.notifyNetworkStatus(getDefaultNetworks(), + snapshots, activeIface, Arrays.asList(underlyingNetworkInfos)); } catch (Exception ignored) { } } @@ -8186,7 +8225,7 @@ public class ConnectivityService extends IConnectivityManager.Stub TestNetworkService.enforceTestNetworkPermissions(mContext); if (mTNS == null) { - mTNS = new TestNetworkService(mContext, mNMS); + mTNS = new TestNetworkService(mContext); } return mTNS; @@ -8272,24 +8311,16 @@ public class ConnectivityService extends IConnectivityManager.Stub final ConnectivityReportEvent reportEvent = (ConnectivityReportEvent) msg.obj; - // This is safe because {@link - // NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} receives a - // PersistableBundle and converts it to the Bundle in the incoming Message. If - // {@link NetworkMonitorCallbacks#notifyNetworkTested} is called, msg.data will - // not be set. This is also safe, as msg.getData() will return an empty Bundle. - final PersistableBundle extras = new PersistableBundle(msg.getData()); - handleNetworkTestedWithExtras(reportEvent, extras); + handleNetworkTestedWithExtras(reportEvent, reportEvent.mExtras); break; } case EVENT_DATA_STALL_SUSPECTED: { final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); + final Pair<Long, PersistableBundle> arg = + (Pair<Long, PersistableBundle>) msg.obj; if (nai == null) break; - // This is safe because NetworkMonitorCallbacks#notifyDataStallSuspected - // receives a PersistableBundle and converts it to the Bundle in the incoming - // Message. - final PersistableBundle extras = new PersistableBundle(msg.getData()); - handleDataStallSuspected(nai, (long) msg.obj, msg.arg1, extras); + handleDataStallSuspected(nai, arg.first, msg.arg1, arg.second); break; } case EVENT_NETWORK_CONNECTIVITY_REPORTED: { @@ -8353,10 +8384,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private static class ConnectivityReportEvent { private final long mTimestampMillis; @NonNull private final NetworkAgentInfo mNai; + private final PersistableBundle mExtras; - private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai) { + private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai, + PersistableBundle p) { mTimestampMillis = timestampMillis; mNai = nai; + mExtras = p; } } @@ -8661,9 +8695,23 @@ public class ConnectivityService extends IConnectivityManager.Stub private class NetdCallback extends BaseNetdUnsolicitedEventListener { @Override - public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, + public void onInterfaceClassActivityChanged(boolean isActive, int transportType, long timestampNs, int uid) { - mNetworkActivityTracker.setAndReportNetworkActive(isActive, timerLabel, timestampNs); + mNetworkActivityTracker.setAndReportNetworkActive(isActive, transportType, timestampNs); + } + + @Override + public void onInterfaceLinkStateChanged(String iface, boolean up) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { + nai.clatd.interfaceLinkStateChanged(iface, up); + } + } + + @Override + public void onInterfaceRemoved(String iface) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { + nai.clatd.interfaceRemoved(iface); + } } } @@ -8697,7 +8745,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } LegacyNetworkActivityTracker(@NonNull Context context, @NonNull Handler handler, - @NonNull INetworkManagementService nms, @NonNull INetd netd) { + @NonNull INetd netd) { mContext = context; mNetd = netd; mHandler = handler; @@ -9019,23 +9067,27 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleSetOemNetworkPreference( @NonNull final OemNetworkPreferences preference, - @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException { + @Nullable final IOnSetOemNetworkPreferenceListener listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); if (DBG) { log("set OEM network preferences :" + preference.toString()); } final ArraySet<NetworkRequestInfo> nris = new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); - updateDefaultNetworksForOemNetworkPreference(nris); + replaceDefaultNetworkRequestsForPreference(nris); mOemNetworkPreferences = preference; // TODO http://b/176496396 persist data to shared preferences. if (null != listener) { - listener.onComplete(); + try { + listener.onComplete(); + } catch (RemoteException e) { + loge("Can't send onComplete in handleSetOemNetworkPreference", e); + } } } - private void updateDefaultNetworksForOemNetworkPreference( + private void replaceDefaultNetworkRequestsForPreference( @NonNull final Set<NetworkRequestInfo> nris) { // Pass in a defensive copy as this collection will be updated on remove. handleRemoveNetworkRequests(new ArraySet<>(mDefaultNetworkRequests)); @@ -9047,10 +9099,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultNetworkRequests.addAll(nris); final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate = getPerAppCallbackRequestsToUpdate(); - handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate); final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris); nrisToRegister.addAll( createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate)); + handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate); handleRegisterNetworkRequests(nrisToRegister); } @@ -9121,6 +9173,14 @@ public class ConnectivityService extends IConnectivityManager.Stub return callbackRequestsToRegister; } + private static void setNetworkRequestUids(@NonNull final List<NetworkRequest> requests, + @NonNull final Set<UidRange> uids) { + final Set<UidRange> ranges = new ArraySet<>(uids); + for (final NetworkRequest req : requests) { + req.networkCapabilities.setUids(ranges); + } + } + /** * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}. */ @@ -9149,6 +9209,14 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull final OemNetworkPreferences preference) { final SparseArray<Set<Integer>> uids = new SparseArray<>(); final PackageManager pm = mContext.getPackageManager(); + final List<UserHandle> users = + mContext.getSystemService(UserManager.class).getUserHandles(true); + if (null == users || users.size() == 0) { + if (VDBG || DDBG) { + log("No users currently available for setting the OEM network preference."); + } + return uids; + } for (final Map.Entry<String, Integer> entry : preference.getNetworkPreferences().entrySet()) { @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue(); @@ -9157,7 +9225,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!uids.contains(pref)) { uids.put(pref, new ArraySet<>()); } - uids.get(pref).add(uid); + for (final UserHandle ui : users) { + // Add the rules for all users as this policy is device wide. + uids.get(pref).add(UserHandle.getUid(ui, uid)); + } } catch (PackageManager.NameNotFoundException e) { // Although this may seem like an error scenario, it is ok that uninstalled // packages are sent on a network preference as the system will watch for @@ -9197,7 +9268,11 @@ public class ConnectivityService extends IConnectivityManager.Stub + " called with invalid preference of " + preference); } - setOemNetworkRequestUids(requests, uids); + final ArraySet ranges = new ArraySet<Integer>(); + for (final int uid : uids) { + ranges.add(new UidRange(uid, uid)); + } + setNetworkRequestUids(requests, ranges); return new NetworkRequestInfo(requests); } @@ -9230,16 +9305,5 @@ public class ConnectivityService extends IConnectivityManager.Stub netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); return netCap; } - - private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests, - @NonNull final Set<Integer> uids) { - final Set<UidRange> ranges = new ArraySet<>(); - for (final int uid : uids) { - ranges.add(new UidRange(uid, uid)); - } - for (final NetworkRequest req : requests) { - req.networkCapabilities.setUids(ranges); - } - } } } diff --git a/services/core/java/com/android/server/ConnectivityServiceInitializer.java b/services/core/java/com/android/server/ConnectivityServiceInitializer.java index 0779f7117d82..b9922087109f 100644 --- a/services/core/java/com/android/server/ConnectivityServiceInitializer.java +++ b/services/core/java/com/android/server/ConnectivityServiceInitializer.java @@ -20,9 +20,6 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import android.content.Context; -import android.net.INetworkStatsService; -import android.os.INetworkManagementService; -import android.os.ServiceManager; import android.util.Log; /** @@ -38,8 +35,7 @@ public final class ConnectivityServiceInitializer extends SystemService { // Load JNI libraries used by ConnectivityService and its dependencies System.loadLibrary("service-connectivity"); // TODO: Define formal APIs to get the needed services. - mConnectivity = new ConnectivityService(context, getNetworkManagementService(), - getNetworkStatsService()); + mConnectivity = new ConnectivityService(context); } @Override @@ -48,14 +44,4 @@ public final class ConnectivityServiceInitializer extends SystemService { publishBinderService(Context.CONNECTIVITY_SERVICE, mConnectivity, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); } - - private INetworkManagementService getNetworkManagementService() { - return INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); - } - - private INetworkStatsService getNetworkStatsService() { - return INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - } } diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index 2906ceebca58..906702866889 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -839,23 +839,34 @@ public abstract class IntentResolver<F, R extends Object> { } }; + // Helper method to copy some of the maps. + private static <E> void copyInto(ArrayMap<String, E[]> l, ArrayMap<String, E[]> r) { + final int end = r.size(); + l.ensureCapacity(end); + for (int i = 0; i < end; i++) { + final E[] val = r.valueAt(i); + final String key = r.keyAt(i); + l.put(key, Arrays.copyOf(val, val.length)); + } + } + // Make <this> a copy of <orig>. The presumption is that <this> is empty but all // arrays are cleared out explicitly, just to be sure. protected void copyFrom(IntentResolver orig) { mFilters.clear(); mFilters.addAll(orig.mFilters); mTypeToFilter.clear(); - mTypeToFilter.putAll(orig.mTypeToFilter); + copyInto(mTypeToFilter, orig.mTypeToFilter); mBaseTypeToFilter.clear(); - mBaseTypeToFilter.putAll(orig.mBaseTypeToFilter); + copyInto(mBaseTypeToFilter, orig.mBaseTypeToFilter); mWildTypeToFilter.clear(); - mWildTypeToFilter.putAll(orig.mWildTypeToFilter); + copyInto(mWildTypeToFilter, orig.mWildTypeToFilter); mSchemeToFilter.clear(); - mSchemeToFilter.putAll(orig.mSchemeToFilter); + copyInto(mSchemeToFilter, orig.mSchemeToFilter); mActionToFilter.clear(); - mActionToFilter.putAll(orig.mActionToFilter); + copyInto(mActionToFilter, orig.mActionToFilter); mTypedActionToFilter.clear(); - mTypedActionToFilter.putAll(orig.mTypedActionToFilter); + copyInto(mTypedActionToFilter, orig.mTypedActionToFilter); } /** diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 44054088d937..10d6570929ed 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -44,7 +44,6 @@ import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENAB import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; -import android.net.ConnectivityManager; import android.net.INetd; import android.net.INetdUnsolicitedEventListener; import android.net.INetworkManagementEventObserver; @@ -54,7 +53,6 @@ import android.net.InterfaceConfiguration; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.LinkAddress; -import android.net.Network; import android.net.NetworkPolicyManager; import android.net.NetworkStack; import android.net.NetworkStats; @@ -974,19 +972,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } @Override - public void setDnsForwarders(Network network, String[] dns) { - NetworkStack.checkNetworkStackPermission(mContext); - - int netId = (network != null) ? network.netId : ConnectivityManager.NETID_UNSET; - - try { - mNetdService.tetherDnsSet(netId, dns); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override public String[] getDnsForwarders() { NetworkStack.checkNetworkStackPermission(mContext); try { @@ -1775,27 +1760,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } @Override - public void addLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid) { - NetworkStack.checkNetworkStackPermission(mContext); - - final LinkAddress la = routeInfo.getDestinationLinkAddress(); - final String ifName = routeInfo.getInterface(); - final String dst = la.toString(); - final String nextHop; - - if (routeInfo.hasGateway()) { - nextHop = routeInfo.getGateway().getHostAddress(); - } else { - nextHop = ""; - } - try { - mNetdService.networkAddLegacyRoute(netId, ifName, dst, nextHop, uid); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override public void allowProtect(int uid) { NetworkStack.checkNetworkStackPermission(mContext); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 233a50d417ad..27b648e53a38 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -24,6 +24,10 @@ import static android.app.AppOpsManager.OP_LEGACY_STORAGE; import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES; import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE; +import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; +import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.app.PendingIntent.FLAG_ONE_SHOT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; @@ -55,6 +59,7 @@ import android.app.AnrController; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.KeyguardManager; +import android.app.PendingIntent; import android.app.admin.SecurityLog; import android.app.usage.StorageStatsManager; import android.content.BroadcastReceiver; @@ -573,6 +578,12 @@ class StorageManagerService extends IStorageManager.Stub */ private static final int PBKDF2_HASH_ROUNDS = 1024; + private static final String ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY = + "anr_delay_millis"; + + private static final String ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY = + "anr_delay_notify_external_storage_service"; + /** * Mounted OBB tracking information. Used to track the current state of all * OBBs. @@ -943,25 +954,51 @@ class StorageManagerService extends IStorageManager.Stub } } - // TODO(b/170486601): Check transcoding status based on events pushed from the MediaProvider private class ExternalStorageServiceAnrController implements AnrController { @Override public long getAnrDelayMillis(String packageName, int uid) { - int delay = SystemProperties.getInt("sys.fuse.transcode_anr_delay", 0); - Log.d(TAG, "getAnrDelayMillis: " + packageName + ". Delaying for " + delay + "ms"); + if (!isAppIoBlocked(uid)) { + return 0; + } + + int delay = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 0); + Slog.v(TAG, "getAnrDelayMillis for " + packageName + ". " + delay + "ms"); return delay; } @Override public void onAnrDelayStarted(String packageName, int uid) { - Log.d(TAG, "onAnrDelayStarted: " + packageName); + if (!isAppIoBlocked(uid)) { + return; + } + + boolean notifyExternalStorageService = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + ANR_DELAY_NOTIFY_EXTERNAL_STORAGE_SERVICE_DEVICE_CONFIG_KEY, true); + if (notifyExternalStorageService) { + Slog.d(TAG, "onAnrDelayStarted for " + packageName + + ". Notifying external storage service"); + try { + mStorageSessionController.notifyAnrDelayStarted(packageName, uid, 0 /* tid */, + StorageManager.APP_IO_BLOCKED_REASON_TRANSCODING); + } catch (ExternalStorageServiceException e) { + Slog.e(TAG, "Failed to notify ANR delay started for " + packageName, e); + } + } else { + // TODO(b/170973510): Implement framework spinning dialog for ANR delay + } } @Override public boolean onAnrDelayCompleted(String packageName, int uid) { - boolean show = SystemProperties.getBoolean("sys.fuse.transcode_anr_dialog_show", true); - Log.d(TAG, "onAnrDelayCompleted: " + packageName + ". Show: " + show); - return show; + if (isAppIoBlocked(uid)) { + Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Showing ANR dialog..."); + return true; + } else { + Slog.d(TAG, "onAnrDelayCompleted for " + packageName + ". Skipping ANR dialog..."); + return false; + } } } @@ -3371,6 +3408,54 @@ class StorageManagerService extends IStorageManager.Stub } } + /** + * Returns PendingIntent which can be used by Apps with MANAGE_EXTERNAL_STORAGE permission + * to launch the manageSpaceActivity of the App specified by packageName. + */ + @Override + @Nullable + public PendingIntent getManageSpaceActivityIntent( + @NonNull String packageName, int requestCode) { + // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API. + enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE); + + // We want to call the manageSpaceActivity as a SystemService and clear identity + // of the calling App + int originalUid = Binder.getCallingUidOrThrow(); + long token = Binder.clearCallingIdentity(); + + try { + ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, + UserHandle.getUserId(originalUid)); + if (appInfo == null) { + throw new IllegalArgumentException( + "Invalid packageName"); + } + if (appInfo.manageSpaceActivityName == null) { + Log.i(TAG, packageName + " doesn't have a manageSpaceActivity"); + return null; + } + Context targetAppContext = mContext.createPackageContext(packageName, 0); + + Intent intent = new Intent(Intent.ACTION_DEFAULT); + intent.setClassName(packageName, + appInfo.manageSpaceActivityName); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + + PendingIntent activity = PendingIntent.getActivity(targetAppContext, requestCode, + intent, + FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); + return activity; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException( + "packageName not found"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + /** Not thread safe */ class AppFuseMountScope extends AppFuseBridge.MountScope { private boolean mMounted = false; @@ -4637,5 +4722,19 @@ class StorageManagerService extends IStorageManager.Stub Binder.restoreCallingIdentity(token); } } + + @Override + public List<String> getPrimaryVolumeIds() { + final List<String> primaryVolumeIds = new ArrayList<>(); + synchronized (mLock) { + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (vol.isPrimary()) { + primaryVolumeIds.add(vol.getId()); + } + } + } + return primaryVolumeIds; + } } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 5a5f1a3f3723..978bd643f9b7 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -72,6 +72,7 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; @@ -153,7 +154,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - boolean matchPhoneStateListenerEvent(int event) { + boolean matchTelephonyCallbackEvent(int event) { return (callback != null) && (this.eventList.contains(event)); } @@ -198,8 +199,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { public int getRegistrationLimit() { return Binder.withCleanCallingIdentity(() -> DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY, - PhoneStateListener.FLAG_PER_PID_REGISTRATION_LIMIT, - PhoneStateListener.DEFAULT_PER_PID_REGISTRATION_LIMIT)); + TelephonyCallback.FLAG_PER_PID_REGISTRATION_LIMIT, + TelephonyCallback.DEFAULT_PER_PID_REGISTRATION_LIMIT)); } /** @@ -210,7 +211,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { */ public boolean isRegistrationLimitEnabledInPlatformCompat(int uid) { return Binder.withCleanCallingIdentity(() -> CompatChanges.isChangeEnabled( - PhoneStateListener.PHONE_STATE_LISTENER_LIMIT_CHANGE_ID, uid)); + TelephonyCallback.PHONE_STATE_LISTENER_LIMIT_CHANGE_ID, uid)); } } @@ -332,37 +333,37 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { static { REQUIRE_PRECISE_PHONE_STATE_PERMISSION = new HashSet<Integer>(); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED); + TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED); + TelephonyCallback.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED); + TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED); + TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED); + TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED); - REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE); - REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED); + TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED); + REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(TelephonyCallback.EVENT_REGISTRATION_FAILURE); + REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(TelephonyCallback.EVENT_BARRING_INFO_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); + TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( - PhoneStateListener.EVENT_DATA_ENABLED_CHANGED); + TelephonyCallback.EVENT_DATA_ENABLED_CHANGED); } private boolean isLocationPermissionRequired(Set<Integer> events) { - return events.contains(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED) - || events.contains(PhoneStateListener.EVENT_CELL_INFO_CHANGED) - || events.contains(PhoneStateListener.EVENT_REGISTRATION_FAILURE) - || events.contains(PhoneStateListener.EVENT_BARRING_INFO_CHANGED); + return events.contains(TelephonyCallback.EVENT_CELL_LOCATION_CHANGED) + || events.contains(TelephonyCallback.EVENT_CELL_INFO_CHANGED) + || events.contains(TelephonyCallback.EVENT_REGISTRATION_FAILURE) + || events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED); } private boolean isPhoneStatePermissionRequired(Set<Integer> events) { - return events.contains(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED) - || events.contains(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED) - || events.contains(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED) - || events.contains(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED); + return events.contains(TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED) + || events.contains(TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED) + || events.contains(TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED) + || events.contains(TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED); } private boolean isPrecisePhoneStatePermissionRequired(Set<Integer> events) { @@ -375,14 +376,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } private boolean isActiveEmergencySessionPermissionRequired(Set<Integer> events) { - return events.contains(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL) - || events.contains(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS); + return events.contains(TelephonyCallback.EVENT_OUTGOING_EMERGENCY_CALL) + || events.contains(TelephonyCallback.EVENT_OUTGOING_EMERGENCY_SMS); } private boolean isPrivilegedPhoneStatePermissionRequired(Set<Integer> events) { - return events.contains(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED) - || events.contains(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED) - || events.contains(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED); + return events.contains(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED) + || events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED) + || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED); } private static final int MSG_USER_SWITCHED = 1; @@ -903,7 +904,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId); } if (notifyNow && validatePhoneId(phoneId)) { - if (events.contains(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)){ try { if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]); ServiceState rawSs = new ServiceState(mServiceState[phoneId]); @@ -920,7 +921,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED)) { try { if (mSignalStrength[phoneId] != null) { int gsmSignalStrength = mSignalStrength[phoneId] @@ -933,7 +934,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) { + TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) { try { r.callback.onMessageWaitingIndicatorChanged( mMessageWaiting[phoneId]); @@ -942,7 +943,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) { + TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) { try { r.callback.onCallForwardingIndicatorChanged( mCallForwarding[phoneId]); @@ -951,7 +952,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (validateEventAndUserLocked( - r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)) { + r, TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)) { try { if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]); if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE) @@ -963,7 +964,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_CALL_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_CALL_STATE_CHANGED)) { try { r.callback.onCallStateChanged(mCallState[phoneId], getCallIncomingNumber(r, phoneId)); @@ -971,7 +972,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED)) { try { r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId], mDataConnectionNetworkType[phoneId]); @@ -979,14 +980,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED)) { try { r.callback.onDataActivity(mDataActivity[phoneId]); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED)) { try { if (mSignalStrength[phoneId] != null) { r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]); @@ -996,7 +997,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { + TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { updateReportSignalStrengthDecision(r.subId); try { if (mSignalStrength[phoneId] != null) { @@ -1007,7 +1008,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (validateEventAndUserLocked( - r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)) { + r, TelephonyCallback.EVENT_CELL_INFO_CHANGED)) { try { if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = " + mCellInfo.get(phoneId)); @@ -1019,14 +1020,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED)) { try { r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED)) { try { r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId], mCallPreciseDisconnectCause[phoneId]); @@ -1034,7 +1035,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)) { try { r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId)); } catch (RemoteException ex) { @@ -1042,7 +1043,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)) { + TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)) { try { for (PreciseDataConnectionState pdcs : mPreciseDataConnectionStates.get(phoneId).values()) { @@ -1052,14 +1053,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)) { try { r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)) { try { r.callback.onVoiceActivationStateChanged( mVoiceActivationState[phoneId]); @@ -1067,21 +1068,21 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED)) { try { r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) { try { r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)) { try { if (mTelephonyDisplayInfos[phoneId] != null) { r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[phoneId]); @@ -1090,14 +1091,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)) { try { r.callback.onEmergencyNumberListChanged(mEmergencyNumberList); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_PHONE_CAPABILITY_CHANGED)) { try { r.callback.onPhoneCapabilityChanged(mPhoneCapability); } catch (RemoteException ex) { @@ -1105,35 +1106,35 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) { + TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) { try { r.callback.onActiveDataSubIdChanged(mActiveDataSubId); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)) { try { r.callback.onRadioPowerStateChanged(mRadioPowerState); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED)) { try { r.callback.onSrvccStateChanged(mSrvccState[phoneId]); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) { try { r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); } catch (RemoteException ex) { remove(r.binder); } } - if (events.contains(PhoneStateListener.EVENT_BARRING_INFO_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED)) { BarringInfo barringInfo = mBarringInfo.get(phoneId); BarringInfo biNoLocation = barringInfo != null ? barringInfo.createLocationInfoSanitizedCopy() : null; @@ -1147,7 +1148,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)) { + TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)) { try { r.callback.onPhysicalChannelConfigChanged( mPhysicalChannelConfigs); @@ -1156,7 +1157,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_DATA_ENABLED_CHANGED)) { + TelephonyCallback.EVENT_DATA_ENABLED_CHANGED)) { try { r.callback.onDataEnabledChanged( mIsDataEnabled[phoneId], mDataEnabledReason[phoneId]); @@ -1165,7 +1166,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if (events.contains( - PhoneStateListener.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)) { + TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)) { try { r.callback.onAllowedNetworkTypesChanged(mAllowedNetworkTypesList); } catch (RemoteException ex) { @@ -1183,8 +1184,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { // If any of the system clients wants to always listen to signal strength, // we need to set it on. - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { telephonyManager.createForSubscriptionId(subscriptionId) .setAlwaysReportSignalStrength(true); return; @@ -1233,7 +1234,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { throw new IllegalStateException(errorMsg); } } else if (doesLimitApply && numRecordsForPid - >= PhoneStateListener.DEFAULT_PER_PID_REGISTRATION_LIMIT / 2) { + >= TelephonyCallback.DEFAULT_PER_PID_REGISTRATION_LIMIT / 2) { // Log the warning independently of the dynamically set limit -- apps shouldn't be // doing this regardless of whether we're throwing them an exception for it. Rlog.w(TAG, "Pid " + callingPid + " has exceeded half the number of permissible" @@ -1284,8 +1285,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // Every time a client that is registrating to always receive the signal // strength is removed from registry records, we need to check if // the signal strength decision needs to update on its slot. - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { updateReportSignalStrengthDecision(r.subId); } return; @@ -1305,7 +1306,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent(PhoneStateListener.EVENT_CALL_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent(TelephonyCallback.EVENT_CALL_STATE_CHANGED) && (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) { try { // Ensure the listener has read call log permission; if they do not return @@ -1340,7 +1341,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallState[phoneId] = state; mCallIncomingNumber[phoneId] = incomingNumber; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent(PhoneStateListener.EVENT_CALL_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent(TelephonyCallback.EVENT_CALL_STATE_CHANGED) && (r.subId == subId) && (r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) { try { @@ -1382,8 +1383,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId + " phoneId=" + phoneId + " state=" + state); } - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_SERVICE_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_SERVICE_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { @@ -1444,8 +1445,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } try { if ((activationType == SIM_ACTIVATION_TYPE_VOICE) - && r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED) + && r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { if (DBG) { log("notifyVoiceActivationStateForPhoneId: callback.onVASC r=" + r @@ -1455,8 +1456,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callback.onVoiceActivationStateChanged(activationState); } if ((activationType == SIM_ACTIVATION_TYPE_DATA) - && r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED) + && r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { if (DBG) { log("notifyDataActivationStateForPhoneId: callback.onDASC r=" + r @@ -1495,11 +1496,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("notifySignalStrengthForPhoneId: r=" + r + " subId=" + subId + " phoneId=" + phoneId + " ss=" + signalStrength); } - if ((r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED) - || r.matchPhoneStateListenerEvent( - PhoneStateListener. - EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) + if ((r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED) + || r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) && idMatch(r.subId, subId, phoneId)) { try { if (DBG) { @@ -1512,8 +1512,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mRemoveList.add(r.binder); } } - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { int gsmSignalStrength = signalStrength.getGsmSignalStrength(); @@ -1559,8 +1559,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId); } for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onCarrierNetworkChange(active); @@ -1592,7 +1592,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellInfo.set(phoneId, cellInfo); for (Record r : mRecords) { if (validateEventAndUserLocked( - r, PhoneStateListener.EVENT_CELL_INFO_CHANGED) + r, TelephonyCallback.EVENT_CELL_INFO_CHANGED) && idMatch(r.subId, subId, phoneId) && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE) && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) { @@ -1625,8 +1625,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mMessageWaiting[phoneId] = mwi; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onMessageWaitingIndicatorChanged(mwi); @@ -1652,8 +1652,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mUserMobileDataState[phoneId] = state; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onUserMobileDataStateChanged(state); @@ -1691,8 +1691,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mTelephonyDisplayInfos[phoneId] = telephonyDisplayInfo; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED) && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) { try { r.callback.onDisplayInfoChanged(telephonyDisplayInfo); @@ -1723,8 +1723,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mCallForwarding[phoneId] = cfi; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onCallForwardingIndicatorChanged(cfi); @@ -1752,8 +1752,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mDataActivity[phoneId] = state; for (Record r : mRecords) { // Notify by correct subId. - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onDataActivity(state); @@ -1800,8 +1800,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log(str); mLocalLog.log(str); for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { if (DBG) { @@ -1825,8 +1825,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { .remove(key); if (!Objects.equals(oldState, preciseState)) { for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onPreciseDataConnectionStateChanged(preciseState); @@ -1872,7 +1872,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellIdentity[phoneId] = cellIdentity; for (Record r : mRecords) { if (validateEventAndUserLocked( - r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED) + r, TelephonyCallback.EVENT_CELL_LOCATION_CHANGED) && idMatch(r.subId, subId, phoneId) && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE) && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) { @@ -1925,8 +1925,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]); @@ -1934,8 +1934,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mRemoveList.add(r.binder); } } - if (notifyCallAttributes && r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED) + if (notifyCallAttributes && r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); @@ -1959,8 +1959,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallDisconnectCause[phoneId] = disconnectCause; mCallPreciseDisconnectCause[phoneId] = preciseDisconnectCause; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent(PhoneStateListener - .LISTEN_CALL_DISCONNECT_CAUSES) && idMatch(r.subId, subId, phoneId)) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED) + && idMatch(r.subId, subId, phoneId)) { try { r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId], mCallPreciseDisconnectCause[phoneId]); @@ -1983,8 +1984,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mImsReasonInfo.set(phoneId, imsReasonInfo); for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { @@ -2015,8 +2016,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mSrvccState[phoneId] = state; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_SRVCC_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_SRVCC_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { @@ -2044,8 +2045,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (VDBG) { log("notifyOemHookRawEventForSubscriber: r=" + r + " subId=" + subId); } - if ((r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_OEM_HOOK_RAW)) + if ((r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_OEM_HOOK_RAW)) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onOemHookRawEvent(rawData); @@ -2072,8 +2073,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mPhoneCapability = capability; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED)) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_PHONE_CAPABILITY_CHANGED)) { try { r.callback.onPhoneCapabilityChanged(capability); } catch (RemoteException ex) { @@ -2097,8 +2098,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mActiveDataSubId = activeDataSubId; synchronized (mRecords) { for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) { try { r.callback.onActiveDataSubIdChanged(activeDataSubId); } catch (RemoteException ex) { @@ -2124,8 +2125,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mRadioPowerState = state; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onRadioPowerStateChanged(state); @@ -2153,8 +2154,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mEmergencyNumberList = tm.getEmergencyNumberList(); for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onEmergencyNumberListChanged(mEmergencyNumberList); @@ -2185,8 +2186,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } for (Record r : mRecords) { // Send to all listeners regardless of subscription - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL)) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_OUTGOING_EMERGENCY_CALL)) { try { r.callback.onOutgoingEmergencyCall(emergencyNumber, subId); } catch (RemoteException ex) { @@ -2204,13 +2205,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyOutgoingEmergencySms()")) { return; } + synchronized (mRecords) { if (validatePhoneId(phoneId)) { mOutgoingSmsEmergencyNumber[phoneId] = emergencyNumber; for (Record r : mRecords) { // Send to all listeners regardless of subscription - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS)) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_OUTGOING_EMERGENCY_SMS)) { try { r.callback.onOutgoingEmergencySms(emergencyNumber, subId); } catch (RemoteException ex) { @@ -2239,8 +2241,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { callNetworkType, callQuality); for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); @@ -2270,8 +2272,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { if (validatePhoneId(phoneId)) { for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_REGISTRATION_FAILURE) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_REGISTRATION_FAILURE) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onRegistrationFailed( @@ -2313,8 +2315,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy(); if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo); for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_BARRING_INFO_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_BARRING_INFO_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { @@ -2356,8 +2358,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mPhysicalChannelConfigs.set(phoneId, configs.get(phoneId)); for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { @@ -2386,7 +2388,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { * {@link TelephonyManager}. */ public void notifyDataEnabled(int phoneId, int subId, boolean enabled, - @TelephonyManager.DataEnabledReason int reason) { + @TelephonyManager.DataEnabledReason int reason) { if (!checkNotifyPermission("notifyDataEnabled()")) { return; } @@ -2401,8 +2403,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mIsDataEnabled[phoneId] = enabled; mDataEnabledReason[phoneId] = reason; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_DATA_ENABLED_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DATA_ENABLED_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onDataEnabledChanged(enabled, reason); @@ -2435,8 +2437,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mAllowedNetworkTypesList = allowedNetworkTypesList; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED) + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED) && idMatch(r.subId, subId, phoneId)) { try { if (VDBG) { @@ -2817,7 +2819,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null); } - if ((events.contains(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED))) { + if ((events.contains(TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED))) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH, null); } @@ -2847,7 +2849,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { foregroundUser = ActivityManager.getCurrentUser(); valid = UserHandle.getUserId(r.callerUid) == foregroundUser - && r.matchPhoneStateListenerEvent(event); + && r.matchTelephonyCallbackEvent(event); if (DBG | DBG_LOC) { log("validateEventAndUserLocked: valid=" + valid + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser @@ -2972,7 +2974,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return; } - if ((events.contains(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED))) { + if ((events.contains(TelephonyCallback.EVENT_SERVICE_STATE_CHANGED))) { try { if (VDBG) log("checkPossibleMissNotify: onServiceStateChanged state=" + mServiceState[phoneId]); @@ -2991,9 +2993,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED) + if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED) || events.contains( - PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { + TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) { try { if (mSignalStrength[phoneId] != null) { SignalStrength signalStrength = mSignalStrength[phoneId]; @@ -3008,7 +3010,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED)) { try { if (mSignalStrength[phoneId] != null) { int gsmSignalStrength = mSignalStrength[phoneId] @@ -3025,7 +3027,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (validateEventAndUserLocked(r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)) { + if (validateEventAndUserLocked(r, TelephonyCallback.EVENT_CELL_INFO_CHANGED)) { try { if (DBG_LOC) { log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = " @@ -3040,7 +3042,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (events.contains(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) { try { if (VDBG) { log("checkPossibleMissNotify: onUserMobileDataStateChanged phoneId=" @@ -3052,7 +3054,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (events.contains(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)) { try { if (VDBG) { log("checkPossibleMissNotify: onDisplayInfoChanged phoneId=" @@ -3066,7 +3068,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (events.contains(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) { try { if (VDBG) { log("checkPossibleMissNotify: onMessageWaitingIndicatorChanged phoneId=" @@ -3079,7 +3081,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (events.contains(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) { try { if (VDBG) { log("checkPossibleMissNotify: onCallForwardingIndicatorChanged phoneId=" @@ -3092,7 +3094,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (validateEventAndUserLocked(r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)) { + if (validateEventAndUserLocked(r, TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)) { try { if (DBG_LOC) { log("checkPossibleMissNotify: onCellLocationChanged mCellIdentity = " @@ -3108,7 +3110,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (events.contains(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)) { + if (events.contains(TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED)) { try { if (DBG) { log("checkPossibleMissNotify: onDataConnectionStateChanged(mDataConnectionState" diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index 96f832d26816..55408ea61566 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -32,7 +32,6 @@ import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; -import android.net.NetworkStack; import android.net.RouteInfo; import android.net.StringNetworkSpecifier; import android.net.TestNetworkInterface; @@ -41,7 +40,6 @@ import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.INetworkManagementService; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -51,6 +49,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetdUtils; import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.PermissionUtils; import java.io.UncheckedIOException; import java.net.Inet4Address; @@ -69,7 +68,6 @@ class TestNetworkService extends ITestNetworkManager.Stub { @NonNull private static final AtomicInteger sTestTunIndex = new AtomicInteger(); @NonNull private final Context mContext; - @NonNull private final INetworkManagementService mNMS; @NonNull private final INetd mNetd; @NonNull private final HandlerThread mHandlerThread; @@ -82,14 +80,12 @@ class TestNetworkService extends ITestNetworkManager.Stub { private static native int jniCreateTunTap(boolean isTun, @NonNull String iface); @VisibleForTesting - protected TestNetworkService( - @NonNull Context context, @NonNull INetworkManagementService netManager) { + protected TestNetworkService(@NonNull Context context) { mHandlerThread = new HandlerThread("TestNetworkServiceThread"); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mContext = Objects.requireNonNull(context, "missing Context"); - mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService"); mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance"); mCm = mContext.getSystemService(ConnectivityManager.class); mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(), @@ -324,7 +320,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { try { final long token = Binder.clearCallingIdentity(); try { - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); NetdUtils.setInterfaceUp(mNetd, iface); } 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 8d5d3d939e4b..ad2f52401e93 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -35,6 +35,7 @@ import android.net.vcn.IVcnManagementService; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; +import android.net.vcn.VcnManager; import android.net.vcn.VcnManager.VcnErrorCode; import android.net.vcn.VcnUnderlyingNetworkPolicy; import android.net.wifi.WifiInfo; @@ -724,6 +725,26 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } + private boolean isCallbackPermissioned( + @NonNull VcnStatusCallbackInfo cbInfo, @NonNull ParcelUuid subgroup) { + if (!subgroup.equals(cbInfo.mSubGroup)) { + return false; + } + + if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subgroup, cbInfo.mPkgName)) { + return false; + } + + if (!mLocationPermissionChecker.checkLocationPermission( + cbInfo.mPkgName, + "VcnStatusCallback" /* featureId */, + cbInfo.mUid, + null /* message */)) { + return false; + } + return true; + } + /** Registers the provided callback for receiving VCN status updates. */ @Override public void registerVcnStatusCallback( @@ -758,6 +779,27 @@ public class VcnManagementService extends IVcnManagementService.Stub { } mRegisteredStatusCallbacks.put(cbBinder, cbInfo); + + // now that callback is registered, send it the VCN's current status + final VcnConfig vcnConfig = mConfigs.get(subGroup); + final Vcn vcn = mVcns.get(subGroup); + final int vcnStatus; + if (vcnConfig == null || !isCallbackPermissioned(cbInfo, subGroup)) { + vcnStatus = VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED; + } else if (vcn == null) { + vcnStatus = VcnManager.VCN_STATUS_CODE_INACTIVE; + } else if (vcn.isActive()) { + vcnStatus = VcnManager.VCN_STATUS_CODE_ACTIVE; + } else { + // TODO(b/181789060): create Vcn.getStatus() and Log.WTF() for unknown status + vcnStatus = VcnManager.VCN_STATUS_CODE_SAFE_MODE; + } + + try { + cbInfo.mCallback.onVcnStatusChanged(vcnStatus); + } catch (RemoteException e) { + Slog.d(TAG, "VcnStatusCallback threw on VCN status change", e); + } } } finally { Binder.restoreCallingIdentity(identity); @@ -806,26 +848,6 @@ public class VcnManagementService extends IVcnManagementService.Stub { mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup"); } - private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) { - if (!mSubGroup.equals(cbInfo.mSubGroup)) { - return false; - } - - if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup( - mSubGroup, cbInfo.mPkgName)) { - return false; - } - - if (!mLocationPermissionChecker.checkLocationPermission( - cbInfo.mPkgName, - "VcnStatusCallback" /* featureId */, - cbInfo.mUid, - null /* message */)) { - return false; - } - return true; - } - @Override public void onEnteredSafeMode() { synchronized (mLock) { @@ -838,7 +860,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // Notify all registered StatusCallbacks for this subGroup for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { - if (isCallbackPermissioned(cbInfo)) { + if (isCallbackPermissioned(cbInfo, mSubGroup)) { Binder.withCleanCallingIdentity( () -> cbInfo.mCallback.onVcnStatusChanged( @@ -862,7 +884,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // Notify all registered StatusCallbacks for this subGroup for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { - if (isCallbackPermissioned(cbInfo)) { + if (isCallbackPermissioned(cbInfo, mSubGroup)) { Binder.withCleanCallingIdentity( () -> cbInfo.mCallback.onGatewayConnectionError( diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java index 5d89bf1b1d82..56aabc208027 100644 --- a/services/core/java/com/android/server/VpnManagerService.java +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -47,7 +47,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.security.Credentials; -import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; @@ -60,6 +59,7 @@ import com.android.internal.net.VpnProfile; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.connectivity.Vpn; +import com.android.server.connectivity.VpnProfileStore; import com.android.server.net.LockdownVpnTracker; import java.io.FileDescriptor; @@ -83,7 +83,7 @@ public class VpnManagerService extends IVpnManager.Stub { private final Dependencies mDeps; private final ConnectivityManager mCm; - private final KeyStore mKeyStore; + private final VpnProfileStore mVpnProfileStore; private final INetworkManagementService mNMS; private final INetd mNetd; private final UserManager mUserManager; @@ -114,9 +114,9 @@ public class VpnManagerService extends IVpnManager.Stub { return new HandlerThread("VpnManagerService"); } - /** Returns the KeyStore instance to be used by this class. */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); + /** Return the VpnProfileStore to be used by this class */ + public VpnProfileStore getVpnProfileStore() { + return new VpnProfileStore(); } public INetd getNetd() { @@ -135,7 +135,7 @@ public class VpnManagerService extends IVpnManager.Stub { mHandlerThread = mDeps.makeHandlerThread(); mHandlerThread.start(); mHandler = mHandlerThread.getThreadHandler(); - mKeyStore = mDeps.getKeyStore(); + mVpnProfileStore = mDeps.getVpnProfileStore(); mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); mCm = mContext.getSystemService(ConnectivityManager.class); mNMS = mDeps.getINetworkManagementService(); @@ -289,7 +289,7 @@ public class VpnManagerService extends IVpnManager.Stub { public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); + return mVpns.get(user).provisionVpnProfile(packageName, profile); } } @@ -307,7 +307,7 @@ public class VpnManagerService extends IVpnManager.Stub { public void deleteVpnProfile(@NonNull String packageName) { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { - mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); + mVpns.get(user).deleteVpnProfile(packageName); } } @@ -325,7 +325,7 @@ public class VpnManagerService extends IVpnManager.Stub { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { throwIfLockdownEnabled(); - mVpns.get(user).startVpnProfile(packageName, mKeyStore); + mVpns.get(user).startVpnProfile(packageName); } } @@ -358,7 +358,7 @@ public class VpnManagerService extends IVpnManager.Stub { } synchronized (mVpns) { throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); + mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress); } } @@ -396,7 +396,7 @@ public class VpnManagerService extends IVpnManager.Stub { } private boolean isLockdownVpnEnabled() { - return mKeyStore.contains(Credentials.LOCKDOWN_VPN); + return mVpnProfileStore.get(Credentials.LOCKDOWN_VPN) != null; } @Override @@ -417,14 +417,14 @@ public class VpnManagerService extends IVpnManager.Stub { return true; } - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + byte[] profileTag = mVpnProfileStore.get(Credentials.LOCKDOWN_VPN); if (profileTag == null) { loge("Lockdown VPN configured but cannot be read from keystore"); return false; } String profileName = new String(profileTag); final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); + profileName, mVpnProfileStore.get(Credentials.VPN + profileName)); if (profile == null) { loge("Lockdown VPN configured invalid profile " + profileName); setLockdownTracker(null); @@ -437,7 +437,7 @@ public class VpnManagerService extends IVpnManager.Stub { return false; } setLockdownTracker( - new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); + new LockdownVpnTracker(mContext, mHandler, vpn, profile)); } return true; @@ -495,7 +495,7 @@ public class VpnManagerService extends IVpnManager.Stub { return false; } - return vpn.startAlwaysOnVpn(mKeyStore); + return vpn.startAlwaysOnVpn(); } } @@ -510,7 +510,7 @@ public class VpnManagerService extends IVpnManager.Stub { logw("User " + userId + " has no Vpn configuration"); return false; } - return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + return vpn.isAlwaysOnPackageSupported(packageName); } } @@ -531,11 +531,11 @@ public class VpnManagerService extends IVpnManager.Stub { logw("User " + userId + " has no Vpn configuration"); return false; } - if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) { + if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist)) { return false; } if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + vpn.setAlwaysOnPackage(null, false, null); return false; } } @@ -705,7 +705,8 @@ public class VpnManagerService extends IVpnManager.Stub { loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); + userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, + new VpnProfileStore()); mVpns.put(userId, userVpn); if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { updateLockdownVpn(); @@ -777,7 +778,7 @@ public class VpnManagerService extends IVpnManager.Stub { if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { log("Restarting always-on VPN package " + packageName + " for user " + userId); - vpn.startAlwaysOnVpn(mKeyStore); + vpn.startAlwaysOnVpn(); } } } @@ -798,7 +799,7 @@ public class VpnManagerService extends IVpnManager.Stub { if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { log("Removing always-on VPN package " + packageName + " for user " + userId); - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + vpn.setAlwaysOnPackage(null, false, null); } } } @@ -843,7 +844,7 @@ public class VpnManagerService extends IVpnManager.Stub { if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { final long ident = Binder.clearCallingIdentity(); try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN); mLockdownEnabled = false; setLockdownTracker(null); } finally { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 9930eac5cbd5..73755231c3be 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -279,7 +279,7 @@ public class AccountManagerService mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mHandler = new MessageHandler(injector.getMessageHandlerLooper()); mAuthenticatorCache = mInjector.getAccountAuthenticatorCache(); - mAuthenticatorCache.setListener(this, null /* Handler */); + mAuthenticatorCache.setListener(this, mHandler); sThis.set(this); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d998ebbf4aff..277cb8c877dd 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -86,6 +86,7 @@ import android.app.Service; import android.app.ServiceStartArgs; import android.app.admin.DevicePolicyEventLogger; import android.app.compat.CompatChanges; +import android.app.usage.UsageEvents; import android.appwidget.AppWidgetManagerInternal; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; @@ -2464,6 +2465,10 @@ public final class ActiveServices { s.setAllowedBgFgsStartsByBinding(true); } + if ((flags & Context.BIND_NOT_APP_COMPONENT_USAGE) != 0) { + s.isNotAppComponentUsage = true; + } + if (s.app != null) { updateServiceClientActivitiesLocked(s.app.mServices, c, true); } @@ -3332,6 +3337,14 @@ public final class ActiveServices { return msg; } + // Report usage if binding is from a different package except for explicitly exempted + // bindings + if (!r.appInfo.packageName.equals(r.mRecentCallingPackage) + && !r.isNotAppComponentUsage) { + mAm.mUsageStatsService.reportEvent( + r.packageName, r.userId, UsageEvents.Event.APP_COMPONENT_USED); + } + // Service is now being launched, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7cd494976c94..9e5d7e4c3325 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -50,7 +50,6 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; -import static android.os.PowerWhitelistManager.REASON_UNKNOWN; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.BLUETOOTH_UID; import static android.os.Process.FIRST_APPLICATION_UID; @@ -92,6 +91,7 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; @@ -104,7 +104,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP; @@ -1178,14 +1177,16 @@ public class ActivityManagerService extends IActivityManager.Stub final String tag; final int type; final @ReasonCode int reasonCode; + final int callingUid; PendingTempAllowlist(int targetUid, long duration, @ReasonCode int reasonCode, String tag, - int type) { + int type, int callingUid) { this.targetUid = targetUid; this.duration = duration; this.tag = tag; this.type = type; this.reasonCode = reasonCode; + this.callingUid = callingUid; } void dumpDebug(ProtoOutputStream proto, long fieldId) { @@ -1198,6 +1199,8 @@ public class ActivityManagerService extends IActivityManager.Stub proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TYPE, type); proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.REASON_CODE, reasonCode); + proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.CALLING_UID, + callingUid); proto.end(token); } } @@ -2684,6 +2687,11 @@ public class ActivityManagerService extends IActivityManager.Stub } if (mUsageStatsService != null) { mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(), taskRoot); + if (event == Event.ACTIVITY_RESUMED) { + // Report component usage as an activity is an app component + mUsageStatsService.reportEvent( + activity.getPackageName(), userId, Event.APP_COMPONENT_USED); + } } ContentCaptureManagerInternal contentCaptureService = mContentCaptureService; if (contentCaptureService != null && (event == Event.ACTIVITY_PAUSED @@ -4963,7 +4971,7 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public List<ResolveInfo> queryIntentComponentsForIntentSender( + public ParceledListSlice<ResolveInfo> queryIntentComponentsForIntentSender( IIntentSender pendingResult, int matchFlags) { enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT, "queryIntentComponentsForIntentSender()"); @@ -4981,15 +4989,15 @@ public class ActivityManagerService extends IActivityManager.Stub final int userId = res.key.userId; switch (res.key.type) { case ActivityManager.INTENT_SENDER_ACTIVITY: - return mContext.getPackageManager().queryIntentActivitiesAsUser( - intent, matchFlags, userId); + return new ParceledListSlice<>(mContext.getPackageManager() + .queryIntentActivitiesAsUser(intent, matchFlags, userId)); case ActivityManager.INTENT_SENDER_SERVICE: case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE: - return mContext.getPackageManager().queryIntentServicesAsUser( - intent, matchFlags, userId); + return new ParceledListSlice<>(mContext.getPackageManager() + .queryIntentServicesAsUser(intent, matchFlags, userId)); case ActivityManager.INTENT_SENDER_BROADCAST: - return mContext.getPackageManager().queryBroadcastReceiversAsUser( - intent, matchFlags, userId); + return new ParceledListSlice<>(mContext.getPackageManager() + .queryBroadcastReceiversAsUser(intent, matchFlags, userId)); default: // ActivityManager.INTENT_SENDER_ACTIVITY_RESULT throw new IllegalStateException("Unsupported intent sender type: " + res.key.type); } @@ -6099,6 +6107,10 @@ public class ActivityManagerService extends IActivityManager.Stub updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); } + // Report usage as process is persistent and being started. + mUsageStatsService.reportEvent(info.packageName, UserHandle.getUserId(app.uid), + Event.APP_COMPONENT_USED); + // This package really, really can not be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( @@ -9222,6 +9234,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print(ptw.type); pw.print(" "); pw.print(ptw.reasonCode); + pw.print(" "); + pw.print(ptw.callingUid); } } } @@ -10776,9 +10790,12 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print(" unmapped + "); pw.print(stringifyKBSize(ionPool)); pw.println(" pools)"); + kernelUsed += ionUnmapped; // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being - // set on ION VMAs, therefore consider the entire ION heap as used kernel memory - kernelUsed += ionHeap; + // set on ION VMAs, however it might be included by the memtrack HAL. + // Replace memtrack HAL reported Graphics category with mapped dmabufs + ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS]; + ss[INDEX_TOTAL_PSS] += dmabufMapped; } else { final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); if (totalExportedDmabuf >= 0) { @@ -14501,7 +14518,8 @@ public class ActivityManagerService extends IActivityManager.Stub String reason, int type, int callingUid) { synchronized (mProcLock) { mPendingTempAllowlist.put(targetUid, - new PendingTempAllowlist(targetUid, duration, reasonCode, reason, type)); + new PendingTempAllowlist(targetUid, duration, reasonCode, reason, type, + callingUid)); setUidTempAllowlistStateLSP(targetUid, true); mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget(); @@ -14532,7 +14550,8 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i = 0; i < N; i++) { PendingTempAllowlist ptw = list[i]; mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid, - ptw.duration, ptw.type, true, ptw.reasonCode, ptw.tag); + ptw.duration, ptw.type, true, ptw.reasonCode, ptw.tag, + ptw.callingUid); } } @@ -15179,8 +15198,8 @@ public class ActivityManagerService extends IActivityManager.Stub mFgsStartTempAllowList.add(changingUid, durationMs, reasonCode, reason, callingUid); } - setAppIdTempAllowlistStateLSP(changingUid, adding); } + setAppIdTempAllowlistStateLSP(changingUid, adding); } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index c971bd2ab6d5..5ad77a3a412a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -168,6 +168,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private int mTaskId; private boolean mIsTaskOverlay; private boolean mIsLockTask; + private boolean mAsync; private BroadcastOptions mBroadcastOptions; final boolean mDumping; @@ -342,6 +343,7 @@ final class ActivityManagerShellCommand extends ShellCommand { mTaskId = INVALID_TASK_ID; mIsTaskOverlay = false; mIsLockTask = false; + mAsync = false; mBroadcastOptions = null; return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() { @@ -406,6 +408,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mBroadcastOptions = BroadcastOptions.makeBasic(); } mBroadcastOptions.setBackgroundActivityStartsAllowed(true); + } else if (opt.equals("--async")) { + mAsync = true; } else { return false; } @@ -756,7 +760,9 @@ final class ActivityManagerShellCommand extends ShellCommand { mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null, requiredPermissions, android.app.AppOpsManager.OP_NONE, bundle, true, false, mUserId); - receiver.waitForFinish(); + if (!mAsync) { + receiver.waitForFinish(); + } return 0; } @@ -3180,13 +3186,17 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Stop a Service. Options are:"); pw.println(" --user <USER_ID> | current: Specify which user to run as; if not"); pw.println(" specified then run as the current user."); - pw.println(" broadcast [--user <USER_ID> | all | current] <INTENT>"); + pw.println(" broadcast [--user <USER_ID> | all | current]"); + pw.println(" [--receiver-permission <PERMISSION>]"); + pw.println(" [--allow-background-activity-starts]"); + pw.println(" [--async] <INTENT>"); pw.println(" Send a broadcast Intent. Options are:"); pw.println(" --user <USER_ID> | all | current: Specify which user to send to; if not"); pw.println(" specified then send to all users."); pw.println(" --receiver-permission <PERMISSION>: Require receiver to hold permission."); pw.println(" --allow-background-activity-starts: The receiver may start activities"); pw.println(" even if in the background."); + pw.println(" --async: Send without waiting for the completion of the receiver."); pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]"); pw.println(" [--user <USER_ID> | current]"); pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index f8494d8a7c04..31ea14a73409 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1517,17 +1517,21 @@ public class AppProfiler { long kernelUsed = memInfo.getKernelUsedSizeKb(); final long ionHeap = Debug.getIonHeapsSizeKb(); final long ionPool = Debug.getIonPoolsSizeKb(); + final long dmabufMapped = Debug.getDmabufMappedSizeKb(); if (ionHeap >= 0 && ionPool >= 0) { + final long ionUnmapped = ionHeap - dmabufMapped; memInfoBuilder.append(" ION: "); memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool)); memInfoBuilder.append("\n"); + kernelUsed += ionUnmapped; // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being - // set on ION VMAs, therefore consider the entire ION heap as used kernel memory - kernelUsed += ionHeap; + // set on ION VMAs, however it might be included by the memtrack HAL. + // Replace memtrack HAL reported Graphics category with mapped dmabufs + totalPss -= totalMemtrackGraphics; + totalPss += dmabufMapped; } else { final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); if (totalExportedDmabuf >= 0) { - final long dmabufMapped = Debug.getDmabufMappedSizeKb(); final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped; memInfoBuilder.append("DMA-BUF: "); memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf)); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 167c2b1ad66c..360e1b4e48a8 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -16,6 +16,9 @@ package com.android.server.am; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; + +import android.annotation.NonNull; import android.bluetooth.BluetoothActivityEnergyInfo; import android.content.ContentResolver; import android.content.Context; @@ -25,7 +28,9 @@ import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.State; import android.hardware.power.stats.StateResidency; import android.hardware.power.stats.StateResidencyResult; +import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; +import android.net.Network; import android.net.NetworkCapabilities; import android.os.BatteryStats; import android.os.BatteryStatsInternal; @@ -77,6 +82,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ParseUtils; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.net.BaseNetworkObserver; @@ -291,6 +297,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub return builder.toString(); } + private ConnectivityManager.NetworkCallback mNetworkCallback = + new ConnectivityManager.NetworkCallback() { + @Override + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities) { + final String state = networkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + ? "CONNECTED" : "SUSPENDED"; + noteConnectivityChanged(NetworkCapabilitiesUtils.getDisplayTransport( + networkCapabilities.getTransportTypes()), state); + } + + @Override + public void onLost(Network network) { + noteConnectivityChanged(-1, "DISCONNECTED"); + } + }; + BatteryStatsService(Context context, File systemDir, Handler handler) { // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through. mContext = context; @@ -330,8 +353,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub mWorker.systemServicesReady(); final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); try { nms.registerObserver(mActivityChangeObserver); + cm.registerDefaultNetworkCallback(mNetworkCallback); } catch (RemoteException e) { Slog.e(TAG, "Could not register INetworkManagement event observer " + e); } @@ -346,6 +371,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub } Watchdog.getInstance().addMonitor(this); + + final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler); + dataConnectionStats.startMonitoring(); } private final class LocalService extends BatteryStatsInternal { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 29061930cd84..81c4c8605fb4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -29,6 +29,7 @@ import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IApplicationThread; import android.app.PendingIntent; +import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.ContentResolver; import android.content.IIntentReceiver; @@ -52,6 +53,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.permission.IPermissionManager; +import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; import android.util.SparseIntArray; @@ -928,6 +930,8 @@ public final class BroadcastQueue { } else if (r.intent.getData() != null) { b.append(r.intent.getData()); } + b.append(",reason:"); + b.append(reason); if (DEBUG_BROADCAST) { Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration + " type=" + type + " : " + b.toString()); @@ -1634,6 +1638,13 @@ public final class BroadcastQueue { brOptions.getTemporaryAppAllowlistReason()); } + // Report that a component is used for explicit broadcasts. + if (!r.intent.isExcludingStopped() && r.curComponent != null + && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) { + mService.mUsageStatsService.reportEvent( + r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED); + } + // Broadcast is being executed, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index f43c7f6278c9..2c8794d75795 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -32,6 +32,7 @@ import android.app.AppOpsManager; import android.app.ApplicationExitInfo; import android.app.ContentProviderHolder; import android.app.IApplicationThread; +import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; @@ -57,6 +58,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -412,6 +414,12 @@ public class ContentProviderHelper { final long origId = Binder.clearCallingIdentity(); try { + if (!TextUtils.equals(cpr.appInfo.packageName, callingPackage)) { + // Report component used since a content provider is being bound. + mService.mUsageStatsService.reportEvent( + cpr.appInfo.packageName, userId, Event.APP_COMPONENT_USED); + } + // Content provider is now in use, its package can't be stopped. try { checkTime(startTime, diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java index fbd089c1f0ee..6e39a4c802d9 100644 --- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java +++ b/services/core/java/com/android/server/am/DataConnectionStats.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.connectivity; +package com.android.server.am; import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; @@ -34,11 +34,11 @@ import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.app.IBatteryStats; -import com.android.server.am.BatteryStatsService; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; +/** Class for receiving data connection state to report to {@link BatteryStatsService}. */ public class DataConnectionStats extends BroadcastReceiver { private static final String TAG = "DataConnectionStats"; private static final boolean DEBUG = false; @@ -52,6 +52,7 @@ public class DataConnectionStats extends BroadcastReceiver { private SignalStrength mSignalStrength; private ServiceState mServiceState; private int mDataState = TelephonyManager.DATA_DISCONNECTED; + private int mNrState = NetworkRegistrationInfo.NR_STATE_NONE; public DataConnectionStats(Context context, Handler listenerHandler) { mContext = context; @@ -61,14 +62,14 @@ public class DataConnectionStats extends BroadcastReceiver { new PhoneStateListenerImpl(new PhoneStateListenerExecutor(listenerHandler)); } + /** Start data connection state monitoring. */ public void startMonitoring() { - TelephonyManager phone = - (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager phone = mContext.getSystemService(TelephonyManager.class); phone.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE - | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS - | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_DATA_ACTIVITY); + | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_DATA_ACTIVITY); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SIM_STATE_CHANGED); @@ -99,11 +100,13 @@ public class DataConnectionStats extends BroadcastReceiver { : regInfo.getAccessNetworkTechnology(); // If the device is in NSA NR connection the networkType will report as LTE. // For cell dwell rate metrics, this should report NR instead. - if (regInfo != null && regInfo.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED) { + if (mNrState == NetworkRegistrationInfo.NR_STATE_CONNECTED) { networkType = TelephonyManager.NETWORK_TYPE_NR; } - if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible", - networkType, visible ? "" : "not ")); + if (DEBUG) { + Log.d(TAG, String.format("Noting data connection for network type %s: %svisible", + networkType, visible ? "" : "not ")); + } try { mBatteryStats.notePhoneDataConnectionState(networkType, visible, mServiceState.getState()); @@ -112,7 +115,7 @@ public class DataConnectionStats extends BroadcastReceiver { } } - private final void updateSimState(Intent intent) { + private void updateSimState(Intent intent) { String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE); if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) { mSimState = TelephonyManager.SIM_STATE_ABSENT; @@ -171,6 +174,7 @@ public class DataConnectionStats extends BroadcastReceiver { @Override public void onServiceStateChanged(ServiceState state) { mServiceState = state; + mNrState = state.getNrState(); notePhoneDataConnectionState(); } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 3258f8af0da2..d03a47afed8a 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -329,6 +329,22 @@ class ProcessErrorStateRecord { info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n"); } + // Retrieve controller with max ANR delay from AnrControllers + // Note that we retrieve the controller before dumping stacks because dumping stacks can + // take a few seconds, after which the cause of the ANR delay might have completed and + // there might no longer be a valid ANR controller to cancel the dialog in that case + AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo); + long anrDialogDelayMs = 0; + if (anrController != null) { + String packageName = aInfo.packageName; + int uid = aInfo.uid; + anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid); + // Might execute an async binder call to a system app to show an interim + // ANR progress UI + anrController.onAnrDelayStarted(packageName, uid); + Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName); + } + StringBuilder report = new StringBuilder(); report.append(MemoryPressureUtil.currentPsiState()); ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); @@ -417,20 +433,6 @@ class ProcessErrorStateRecord { return; } - // Retrieve max ANR delay from AnrControllers without the mService lock since the - // controllers might in turn call into apps - AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo); - long anrDialogDelayMs = 0; - if (anrController != null) { - String packageName = aInfo.packageName; - int uid = aInfo.uid; - anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid); - // Might execute an async binder call to a system app to show an interim - // ANR progress UI - anrController.onAnrDelayStarted(packageName, uid); - Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName); - } - synchronized (mService) { // mBatteryStatsService can be null if the AMS is constructed with injector only. This // will only happen in tests. diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 3ab95d131fad..9cd9902f4995 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -107,6 +107,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean delayed; // are we waiting to start this service in the background? boolean fgRequired; // is the service required to go foreground after starting? boolean fgWaiting; // is a timeout for going foreground already scheduled? + boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java b/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java new file mode 100644 index 000000000000..b0335fe404f4 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.apphibernation; + +/** + * App hibernation manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class AppHibernationManagerInternal { + + /** + * @see AppHibernationService#isHibernatingForUser + */ + public abstract boolean isHibernatingForUser(String packageName, int userId); + + /** + * @see AppHibernationService#setHibernatingForUser + */ + public abstract void setHibernatingForUser(String packageName, int userId, + boolean isHibernating); + + /** + * @see AppHibernationService#isHibernatingGlobally + */ + public abstract boolean isHibernatingGlobally(String packageName); + + /** + * @see AppHibernationService#setHibernatingGlobally + */ + public abstract void setHibernatingGlobally(String packageName, boolean isHibernating); +} diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index 32ae87898085..968cf5f1df91 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -59,6 +59,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.LocalServices; import com.android.server.SystemService; import java.io.File; @@ -134,6 +135,8 @@ public final class AppHibernationService extends SystemService { intentFilter.addAction(ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); userAllContext.registerReceiver(mBroadcastReceiver, intentFilter); + + LocalServices.addService(AppHibernationManagerInternal.class, mLocalService); } @Override @@ -545,6 +548,36 @@ public final class AppHibernationService extends SystemService { } } + private final AppHibernationManagerInternal mLocalService = new LocalService(this); + + private static final class LocalService extends AppHibernationManagerInternal { + private final AppHibernationService mService; + + LocalService(AppHibernationService service) { + mService = service; + } + + @Override + public boolean isHibernatingForUser(String packageName, int userId) { + return mService.isHibernatingForUser(packageName, userId); + } + + @Override + public void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { + mService.setHibernatingForUser(packageName, userId, isHibernating); + } + + @Override + public void setHibernatingGlobally(String packageName, boolean isHibernating) { + mService.setHibernatingGlobally(packageName, isHibernating); + } + + @Override + public boolean isHibernatingGlobally(String packageName) { + return mService.isHibernatingGlobally(packageName); + } + } + private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this); static final class AppHibernationServiceStub extends IAppHibernationService.Stub { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 44dcc205a9d0..4a12ff6932de 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -843,11 +843,12 @@ public class AppOpsService extends IAppOpsService.Stub { public void accessed(int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @OpFlags int flags) { - accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName, + long accessTime = System.currentTimeMillis(); + accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags); mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, - tag, uidState, flags); + tag, uidState, flags, accessTime); } /** @@ -951,9 +952,10 @@ public class AppOpsService extends IAppOpsService.Stub { mInProgressEvents = new ArrayMap<>(1); } + long startTime = System.currentTimeMillis(); InProgressStartOpEvent event = mInProgressEvents.get(clientId); if (event == null) { - event = mInProgressStartOpEventPool.acquire(System.currentTimeMillis(), + event = mInProgressStartOpEventPool.acquire(startTime, SystemClock.elapsedRealtime(), clientId, PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags); @@ -967,7 +969,7 @@ public class AppOpsService extends IAppOpsService.Stub { event.numUnfinishedStarts++; mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, - tag, uidState, flags); + tag, uidState, flags, startTime); } /** @@ -1004,14 +1006,16 @@ public class AppOpsService extends IAppOpsService.Stub { OpEventProxyInfo proxyCopy = event.getProxy() != null ? new OpEventProxyInfo(event.getProxy()) : null; + long accessDurationMillis = + SystemClock.elapsedRealtime() - event.getStartElapsedTime(); NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(), - SystemClock.elapsedRealtime() - event.getStartElapsedTime(), proxyCopy); + accessDurationMillis, proxyCopy); mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), finishedEvent); mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, parent.packageName, tag, event.getUidState(), - event.getFlags(), finishedEvent.getDuration()); + event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration()); mInProgressStartOpEventPool.release(event); @@ -2087,8 +2091,8 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void getHistoricalOps(int uid, String packageName, String attributionTag, - List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, - int flags, RemoteCallback callback) { + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback) { PackageManager pm = mContext.getPackageManager(); ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, @@ -2120,14 +2124,14 @@ public class AppOpsService extends IAppOpsService.Stub { // Must not hold the appops lock mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps, - mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, filter, - beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); + mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, + filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @Override public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, - List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, - int flags, RemoteCallback callback) { + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback) { ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, beginTimeMillis, endTimeMillis, flags); Objects.requireNonNull(callback, "callback cannot be null"); @@ -2140,7 +2144,7 @@ public class AppOpsService extends IAppOpsService.Stub { // Must not hold the appops lock mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw, - mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, + mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @@ -4759,6 +4763,7 @@ public class AppOpsService extends IAppOpsService.Stub { mFile.failWrite(stream); } } + mHistoricalRegistry.writeAndClearDiscreteHistory(); } static class Shell extends ShellCommand { @@ -6114,7 +6119,7 @@ public class AppOpsService extends IAppOpsService.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, "clearHistory"); // Must not hold the appops lock - mHistoricalRegistry.clearHistory(); + mHistoricalRegistry.clearAllHistory(); } @Override diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java new file mode 100644 index 000000000000..a99d90883f87 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.appop; + +import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; +import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; +import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; +import static android.app.AppOpsManager.FILTER_BY_UID; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_COARSE_LOCATION; +import static android.app.AppOpsManager.OP_FINE_LOCATION; +import static android.app.AppOpsManager.OP_FLAG_SELF; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; + +import static java.lang.Math.max; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Process; +import android.util.ArrayMap; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.XmlUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * This class manages information about recent accesses to ops for + * permission usage timeline. + * + * The timeline history is kept for limited time (initial default is 24 hours) and + * discarded after that. + * + * Every time state is saved (default is 30 minutes), memory state is dumped to a + * new file and memory state is cleared. Files older than time limit are deleted + * during the process. + * + * When request comes in, files are read and requested information is collected + * and delivered. + */ + +final class DiscreteRegistry { + static final String TIMELINE_FILE_SUFFIX = "tl"; + private static final String TAG = DiscreteRegistry.class.getSimpleName(); + + private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis(); + private static final String TAG_HISTORY = "h"; + private static final String ATTR_VERSION = "v"; + private static final int CURRENT_VERSION = 1; + + private static final String TAG_UID = "u"; + private static final String ATTR_UID = "ui"; + + private static final String TAG_PACKAGE = "p"; + private static final String ATTR_PACKAGE_NAME = "pn"; + + private static final String TAG_OP = "o"; + private static final String ATTR_OP_ID = "op"; + + private static final String TAG_TAG = "a"; + private static final String ATTR_TAG = "at"; + + private static final String TAG_ENTRY = "e"; + private static final String ATTR_NOTE_TIME = "nt"; + private static final String ATTR_NOTE_DURATION = "nd"; + private static final String ATTR_UID_STATE = "us"; + private static final String ATTR_FLAGS = "f"; + + // Lock for read/write access to on disk state + private final Object mOnDiskLock = new Object(); + + //Lock for read/write access to in memory state + private final @NonNull Object mInMemoryLock; + + @GuardedBy("mOnDiskLock") + private final File mDiscreteAccessDir; + + @GuardedBy("mInMemoryLock") + private DiscreteOps mDiscreteOps; + + DiscreteRegistry(Object inMemoryLock) { + mInMemoryLock = inMemoryLock; + mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"), + "discrete"); + createDiscreteAccessDir(); + mDiscreteOps = new DiscreteOps(); + } + + private void createDiscreteAccessDir() { + if (!mDiscreteAccessDir.exists()) { + if (!mDiscreteAccessDir.mkdirs()) { + Slog.e(TAG, "Failed to create DiscreteRegistry directory"); + } + FileUtils.setPermissions(mDiscreteAccessDir.getPath(), + FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1); + } + } + + void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag, + @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, + long accessDuration) { + if (!isDiscreteOp(op, uid, flags)) { + return; + } + synchronized (mInMemoryLock) { + mDiscreteOps.addDiscreteAccess(op, uid, packageName, attributionTag, flags, uidState, + accessTime, accessDuration); + } + } + + void writeAndClearAccessHistory() { + synchronized (mOnDiskLock) { + final File[] files = mDiscreteAccessDir.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + final String fileName = f.getName(); + if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) { + continue; + } + try { + long timestamp = Long.valueOf(fileName.substring(0, + fileName.length() - TIMELINE_FILE_SUFFIX.length())); + if (Instant.now().minus(TIMELINE_HISTORY_CUTOFF, + ChronoUnit.MILLIS).toEpochMilli() > timestamp) { + f.delete(); + Slog.e(TAG, "Deleting file " + fileName); + + } + } catch (Throwable t) { + Slog.e(TAG, "Error while cleaning timeline files: " + t.getMessage() + " " + + t.getStackTrace()); + } + } + } + } + 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())); + } + } + + void getHistoricalDiscreteOps(AppOpsManager.HistoricalOps result, long beginTimeMillis, + 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.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) { + synchronized (mOnDiskLock) { + long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF, + ChronoUnit.MILLIS).toEpochMilli(); + if (historyBeginTimeMillis > endTimeMillis) { + return; + } + beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis); + + final File[] files = mDiscreteAccessDir.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + final String fileName = f.getName(); + if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) { + continue; + } + long timestamp = Long.valueOf(fileName.substring(0, + fileName.length() - TIMELINE_FILE_SUFFIX.length())); + if (timestamp < beginTimeMillis) { + continue; + } + discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter, + packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter); + } + } + } + } + + void clearHistory() { + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + mDiscreteOps = new DiscreteOps(); + } + FileUtils.deleteContentsAndDir(mDiscreteAccessDir); + createDiscreteAccessDir(); + } + } + + public static boolean isDiscreteOp(int op, int uid, @AppOpsManager.OpFlags int flags) { + if (!isDiscreteOp(op)) { + return false; + } + if (!isDiscreteUid(uid)) { + return false; + } + if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) { + return false; + } + return true; + } + + static boolean isDiscreteOp(int op) { + if (op != OP_CAMERA && op != OP_RECORD_AUDIO && op != OP_FINE_LOCATION + && op != OP_COARSE_LOCATION) { + return false; + } + return true; + } + + static boolean isDiscreteUid(int uid) { + if (uid < Process.FIRST_APPLICATION_UID) { + return false; + } + return true; + } + + private final class DiscreteOps { + ArrayMap<Integer, DiscreteUidOps> mUids; + + DiscreteOps() { + mUids = new ArrayMap<>(); + } + + void addDiscreteAccess(int op, int uid, @NonNull String packageName, + @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, + @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { + getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, attributionTag, flags, + uidState, accessTime, accessDuration); + } + + private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) { + int nUids = mUids.size(); + for (int i = 0; i < nUids; i++) { + mUids.valueAt(i).applyToHistory(result, mUids.keyAt(i)); + } + } + + private void writeToFile(File f) throws Exception { + FileOutputStream stream = new FileOutputStream(f); + TypedXmlSerializer out = Xml.resolveSerializer(stream); + + out.startDocument(null, true); + out.startTag(null, TAG_HISTORY); + out.attributeInt(null, ATTR_VERSION, CURRENT_VERSION); + + int nUids = mUids.size(); + for (int i = 0; i < nUids; i++) { + out.startTag(null, TAG_UID); + out.attributeInt(null, ATTR_UID, mUids.keyAt(i)); + mUids.valueAt(i).serialize(out); + out.endTag(null, TAG_UID); + } + out.endTag(null, TAG_HISTORY); + out.endDocument(); + stream.close(); + } + + private DiscreteUidOps getOrCreateDiscreteUidOps(int uid) { + DiscreteUidOps result = mUids.get(uid); + if (result == null) { + result = new DiscreteUidOps(); + mUids.put(uid, result); + } + 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) { + try { + FileInputStream stream = new FileInputStream(f); + TypedXmlPullParser parser = Xml.resolvePullParser(stream); + XmlUtils.beginDocument(parser, TAG_HISTORY); + + // We haven't released version 1 and have more detailed + // accounting - just nuke the current state + final int version = parser.getAttributeInt(null, ATTR_VERSION); + if (version != CURRENT_VERSION) { + throw new IllegalStateException("Dropping unsupported discrete history " + f); + } + + int depth = parser.getDepth(); + 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); + } + } + } catch (Throwable t) { + Slog.e(TAG, "Failed to read file " + f.getName() + " " + t.getMessage() + " " + + Arrays.toString(t.getStackTrace())); + } + + } + } + + private final class DiscreteUidOps { + ArrayMap<String, DiscretePackageOps> mPackages; + + DiscreteUidOps() { + mPackages = new ArrayMap<>(); + } + + void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag, + @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, + long accessTime, long accessDuration) { + getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags, + uidState, accessTime, accessDuration); + } + + private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) { + DiscretePackageOps result = mPackages.get(packageName); + if (result == null) { + result = new DiscretePackageOps(); + mPackages.put(packageName, result); + } + return result; + } + + private void applyToHistory(AppOpsManager.HistoricalOps result, int uid) { + int nPackages = mPackages.size(); + for (int i = 0; i < nPackages; i++) { + mPackages.valueAt(i).applyToHistory(result, uid, mPackages.keyAt(i)); + } + } + + void serialize(TypedXmlSerializer out) throws Exception { + int nPackages = mPackages.size(); + for (int i = 0; i < nPackages; i++) { + out.startTag(null, TAG_PACKAGE); + out.attribute(null, ATTR_PACKAGE_NAME, mPackages.keyAt(i)); + mPackages.valueAt(i).serialize(out); + out.endTag(null, TAG_PACKAGE); + } + } + + 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 { + 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); + } + } + } + } + + private final class DiscretePackageOps { + ArrayMap<Integer, DiscreteOp> mPackageOps; + + DiscretePackageOps() { + mPackageOps = new ArrayMap<>(); + } + + void addDiscreteAccess(int op, @Nullable String attributionTag, + @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, + long accessTime, long accessDuration) { + getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime, + accessDuration); + } + + private DiscreteOp getOrCreateDiscreteOp(int op) { + DiscreteOp result = mPackageOps.get(op); + if (result == null) { + result = new DiscreteOp(); + mPackageOps.put(op, result); + } + return result; + } + + private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, + @NonNull String packageName) { + int nPackageOps = mPackageOps.size(); + for (int i = 0; i < nPackageOps; i++) { + mPackageOps.valueAt(i).applyToHistory(result, uid, packageName, + mPackageOps.keyAt(i)); + } + } + + void serialize(TypedXmlSerializer out) throws Exception { + int nOps = mPackageOps.size(); + for (int i = 0; i < nOps; i++) { + out.startTag(null, TAG_OP); + out.attributeInt(null, ATTR_OP_ID, mPackageOps.keyAt(i)); + mPackageOps.valueAt(i).serialize(out); + out.endTag(null, TAG_OP); + } + } + + void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, + @AppOpsManager.OpFlags int flagsFilter) 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); + } + } + } + } + + private final class DiscreteOp { + ArrayMap<String, List<DiscreteOpEvent>> mAttributedOps; + + DiscreteOp() { + mAttributedOps = new ArrayMap<>(); + } + + 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(); + + 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; + } + if (previousOp.mNoteTime < accessTime) { + break; + } + if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) { + return; + } + } + attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); + } + + private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) { + List<DiscreteOpEvent> result = mAttributedOps.get(attributionTag); + if (result == null) { + result = new ArrayList<>(); + mAttributedOps.put(attributionTag, result); + } + return result; + } + + private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, + @NonNull String packageName, int op) { + int nOps = mAttributedOps.size(); + for (int i = 0; i < nOps; i++) { + String tag = mAttributedOps.keyAt(i); + List<DiscreteOpEvent> events = mAttributedOps.valueAt(i); + int nEvents = events.size(); + for (int j = 0; j < nEvents; j++) { + DiscreteOpEvent event = events.get(j); + result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState, + event.mOpFlag, event.mNoteTime, event.mNoteDuration); + } + } + } + + void serialize(TypedXmlSerializer out) throws Exception { + int nAttributions = mAttributedOps.size(); + for (int i = 0; i < nAttributions; i++) { + out.startTag(null, TAG_TAG); + String tag = mAttributedOps.keyAt(i); + if (tag != null) { + out.attribute(null, ATTR_TAG, mAttributedOps.keyAt(i)); + } + List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i); + int nOps = ops.size(); + for (int j = 0; j < nOps; j++) { + out.startTag(null, TAG_ENTRY); + ops.get(j).serialize(out); + out.endTag(null, TAG_ENTRY); + } + out.endTag(null, TAG_TAG); + } + } + + void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String attributionTagFilter, + @AppOpsManager.OpFlags int flagsFilter) 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(); + while (XmlUtils.nextElementWithin(parser, innerDepth)) { + if (TAG_ENTRY.equals(parser.getName())) { + long noteTime = parser.getAttributeLong(null, ATTR_NOTE_TIME); + long noteDuration = parser.getAttributeLong(null, ATTR_NOTE_DURATION, + -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)) { + continue; + } + DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration, + uidState, opFlags); + events.add(event); + } + } + Collections.sort(events, (a, b) -> a.mNoteTime < b.mNoteTime ? -1 + : (a.mNoteTime == b.mNoteTime ? 0 : 1)); + } + } + } + } + + private final class DiscreteOpEvent { + final long mNoteTime; + final long mNoteDuration; + final @AppOpsManager.UidState int mUidState; + final @AppOpsManager.OpFlags int mOpFlag; + + DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int opFlag) { + mNoteTime = noteTime; + mNoteDuration = noteDuration; + mUidState = uidState; + mOpFlag = opFlag; + } + + private void serialize(TypedXmlSerializer out) throws Exception { + out.attributeLong(null, ATTR_NOTE_TIME, mNoteTime); + if (mNoteDuration != -1) { + out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration); + } + out.attributeInt(null, ATTR_UID_STATE, mUidState); + out.attributeInt(null, ATTR_FLAGS, mOpFlag); + } + } +} + diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 17fd32c57e09..0fcf5ee35e11 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -19,6 +19,8 @@ import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; import static android.app.AppOpsManager.FILTER_BY_UID; +import static android.app.AppOpsManager.HISTORY_FLAG_AGGREGATE; +import static android.app.AppOpsManager.HISTORY_FLAG_DISCRETE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +32,7 @@ import android.app.AppOpsManager.HistoricalOpsRequestFilter; import android.app.AppOpsManager.HistoricalPackageOps; import android.app.AppOpsManager.HistoricalUidOps; import android.app.AppOpsManager.OpFlags; +import android.app.AppOpsManager.OpHistoryFlags; import android.app.AppOpsManager.UidState; import android.content.ContentResolver; import android.database.ContentObserver; @@ -61,9 +64,7 @@ import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; -import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; @@ -71,7 +72,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -85,7 +85,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; /** - * This class managers historical app op state. This includes reading, persistence, + * This class manages historical app op state. This includes reading, persistence, * accounting, querying. * <p> * The history is kept forever in multiple files. Each file time contains the @@ -138,6 +138,8 @@ final class HistoricalRegistry { private static final String PARAMETER_ASSIGNMENT = "="; private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; + private volatile @NonNull DiscreteRegistry mDiscreteRegistry; + @GuardedBy("mLock") private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>(); @@ -199,6 +201,7 @@ final class HistoricalRegistry { HistoricalRegistry(@NonNull Object lock) { mInMemoryLock = lock; + mDiscreteRegistry = new DiscreteRegistry(lock); } HistoricalRegistry(@NonNull HistoricalRegistry other) { @@ -352,36 +355,49 @@ final class HistoricalRegistry { } } - void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName, + void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, - @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, - @OpFlags int flags, @NonNull RemoteCallback callback) { + @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, + long beginTimeMillis, long endTimeMillis, @OpFlags int flags, + @NonNull RemoteCallback callback) { if (!isApiEnabled()) { callback.sendResult(new Bundle()); return; } - synchronized (mOnDiskLock) { - synchronized (mInMemoryLock) { - if (!isPersistenceInitializedMLocked()) { - Slog.e(LOG_TAG, "Interaction before persistence initialized"); - callback.sendResult(new Bundle()); - return; + final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis); + + if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) { + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + if (!isPersistenceInitializedMLocked()) { + Slog.e(LOG_TAG, "Interaction before persistence initialized"); + callback.sendResult(new Bundle()); + return; + } + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, + attributionTag, + opNames, filter, beginTimeMillis, endTimeMillis, flags); + } - final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis); - mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag, - opNames, filter, beginTimeMillis, endTimeMillis, flags); - final Bundle payload = new Bundle(); - payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); - callback.sendResult(payload); } } + + if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) { + mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis, + filter, uid, packageName, opNames, attributionTag, + flags); + } + + final Bundle payload = new Bundle(); + payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); + callback.sendResult(payload); } - void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String attributionTag, - @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, - long beginTimeMillis, long endTimeMillis, @OpFlags int flags, - @NonNull RemoteCallback callback) { + void getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag, + @Nullable String[] opNames, @OpHistoryFlags int historyFlags, + @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, + @OpFlags int flags, @NonNull RemoteCallback callback) { if (!isApiEnabled()) { callback.sendResult(new Bundle()); return; @@ -392,6 +408,8 @@ final class HistoricalRegistry { endTimeMillis = currentTimeMillis; } + final Bundle payload = new Bundle(); + // Argument times are based off epoch start while our internal store is // based off now, so take this into account. final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0); @@ -399,59 +417,68 @@ final class HistoricalRegistry { final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis); - synchronized (mOnDiskLock) { - final List<HistoricalOps> pendingWrites; - final HistoricalOps currentOps; - boolean collectOpsFromDisk; - - synchronized (mInMemoryLock) { - if (!isPersistenceInitializedMLocked()) { - Slog.e(LOG_TAG, "Interaction before persistence initialized"); - callback.sendResult(new Bundle()); - return; - } - - currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis); - if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis() - || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) { - // Some of the current batch falls into the query, so extract that. - final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps); - currentOpsCopy.filter(uid, packageName, attributionTag, opNames, filter, - inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis); - result.merge(currentOpsCopy); - } - pendingWrites = new ArrayList<>(mPendingWrites); - mPendingWrites.clear(); - collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis(); - } + if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) { + mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis, + filter, uid, packageName, opNames, attributionTag, flags); + } - // If the query was only for in-memory state - done. - if (collectOpsFromDisk) { - // If there is a write in flight we need to force it now - persistPendingHistory(pendingWrites); - // Collect persisted state. - final long onDiskAndInMemoryOffsetMillis = currentTimeMillis - - mNextPersistDueTimeMillis + mBaseSnapshotInterval; - final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis - - onDiskAndInMemoryOffsetMillis, 0); - final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis - - onDiskAndInMemoryOffsetMillis, 0); - mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag, - opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags); - } + if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) { + synchronized (mOnDiskLock) { + final List<HistoricalOps> pendingWrites; + final HistoricalOps currentOps; + boolean collectOpsFromDisk; - // Rebase the result time to be since epoch. - result.setBeginAndEndTime(beginTimeMillis, endTimeMillis); + synchronized (mInMemoryLock) { + if (!isPersistenceInitializedMLocked()) { + Slog.e(LOG_TAG, "Interaction before persistence initialized"); + callback.sendResult(new Bundle()); + return; + } - // Send back the result. - final Bundle payload = new Bundle(); - payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); - callback.sendResult(payload); - } + currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis); + if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis() + || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) { + // Some of the current batch falls into the query, so extract that. + final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps); + currentOpsCopy.filter(uid, packageName, attributionTag, opNames, + historyFlags, filter, inMemoryAdjBeginTimeMillis, + inMemoryAdjEndTimeMillis); + result.merge(currentOpsCopy); + } + pendingWrites = new ArrayList<>(mPendingWrites); + mPendingWrites.clear(); + collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis(); + } + + // If the query was only for in-memory state - done. + if (collectOpsFromDisk) { + // If there is a write in flight we need to force it now + persistPendingHistory(pendingWrites); + // Collect persisted state. + final long onDiskAndInMemoryOffsetMillis = currentTimeMillis + - mNextPersistDueTimeMillis + mBaseSnapshotInterval; + final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis + - onDiskAndInMemoryOffsetMillis, 0); + final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis + - onDiskAndInMemoryOffsetMillis, 0); + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, + attributionTag, + opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, + flags); + } + } + } + // Rebase the result time to be since epoch. + result.setBeginAndEndTime(beginTimeMillis, endTimeMillis); + + // Send back the result. + payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); + callback.sendResult(payload); } void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, - @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags) { + @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, + long accessTime) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -461,6 +488,9 @@ final class HistoricalRegistry { getUpdatedPendingHistoricalOpsMLocked( System.currentTimeMillis()).increaseAccessCount(op, uid, packageName, attributionTag, uidState, flags, 1); + + mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, + flags, uidState, accessTime, -1); } } } @@ -482,7 +512,7 @@ final class HistoricalRegistry { void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, - long increment) { + long eventStartTime, long increment) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -492,6 +522,8 @@ final class HistoricalRegistry { getUpdatedPendingHistoricalOpsMLocked( System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags, increment); + mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, + flags, uidState, increment, eventStartTime); } } } @@ -536,7 +568,7 @@ final class HistoricalRegistry { return; } final List<HistoricalOps> history = mPersistence.readHistoryDLocked(); - clearHistory(); + clearHistoricalRegistry(); if (history != null) { final int historySize = history.size(); for (int i = 0; i < historySize; i++) { @@ -605,7 +637,16 @@ final class HistoricalRegistry { } } - void clearHistory() { + void writeAndClearDiscreteHistory() { + mDiscreteRegistry.writeAndClearAccessHistory(); + } + + void clearAllHistory() { + clearHistoricalRegistry(); + mDiscreteRegistry.clearHistory(); + } + + void clearHistoricalRegistry() { synchronized (mOnDiskLock) { synchronized (mInMemoryLock) { if (!isPersistenceInitializedMLocked()) { @@ -692,6 +733,7 @@ final class HistoricalRegistry { } persistPendingHistory(pendingWrites); } + mDiscreteRegistry.writeAndClearAccessHistory(); } private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f5b94177a2d9..8363c9d203d5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6958,7 +6958,10 @@ public class AudioService extends IAudioService.Stub private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) { final VolumeStreamState streamState = mStreamStates[update.mStreamType]; if (update.hasVolumeIndex()) { - final int index = update.getVolumeIndex(); + int index = update.getVolumeIndex(); + if (!checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) { + index = safeMediaVolumeIndex(update.mDevice); + } streamState.setIndex(index, update.mDevice, update.mCaller, // trusted as index is always validated before message is posted true /*hasModifyAudioSettings*/); diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java new file mode 100644 index 000000000000..9f0a2baea91d --- /dev/null +++ b/services/core/java/com/android/server/audio/FadeOutManager.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.audio; + +import android.annotation.NonNull; +import android.media.AudioAttributes; +import android.media.AudioPlaybackConfiguration; +import android.media.VolumeShaper; +import android.util.Log; + +import com.android.internal.util.ArrayUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Class to handle fading out players + */ +public final class FadeOutManager { + + public static final String TAG = "AudioService.FadeOutManager"; + + /*package*/ static final long FADE_OUT_DURATION_MS = 2500; + + private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG; + + private static final VolumeShaper.Configuration FADEOUT_VSHAPE = + new VolumeShaper.Configuration.Builder() + .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID) + .setCurve(new float[]{0.f, 1.0f} /* times */, + new float[]{1.f, 0.0f} /* volumes */) + .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) + .setDuration(FADE_OUT_DURATION_MS) + .build(); + private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = + new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) + .createIfNeeded() + .build(); + + private static final int[] UNFADEABLE_PLAYER_TYPES = { + AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO, + AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL, + }; + + private static final int[] UNFADEABLE_CONTENT_TYPES = { + AudioAttributes.CONTENT_TYPE_SPEECH, + }; + + private static final int[] FADEABLE_USAGES = { + AudioAttributes.USAGE_GAME, + AudioAttributes.USAGE_MEDIA, + }; + + // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp + private static final VolumeShaper.Operation PLAY_SKIP_RAMP = + new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build(); + + /** + * Evaluates whether the player associated with this configuration can and should be faded out + * @param apc the configuration of the player + * @return true if player type and AudioAttributes are compatible with fade out + */ + static boolean canBeFadedOut(@NonNull AudioPlaybackConfiguration apc) { + if (ArrayUtils.contains(UNFADEABLE_PLAYER_TYPES, apc.getPlayerType())) { + if (DEBUG) { Log.i(TAG, "not fading: player type:" + apc.getPlayerType()); } + return false; + } + if (ArrayUtils.contains(UNFADEABLE_CONTENT_TYPES, + apc.getAudioAttributes().getContentType())) { + if (DEBUG) { + Log.i(TAG, "not fading: content type:" + + apc.getAudioAttributes().getContentType()); + } + return false; + } + if (!ArrayUtils.contains(FADEABLE_USAGES, apc.getAudioAttributes().getUsage())) { + if (DEBUG) { + Log.i(TAG, "not fading: usage:" + apc.getAudioAttributes().getUsage()); + } + return false; + } + return true; + } + + /** + * Map of uid (key) to faded out apps (value) + */ + private final HashMap<Integer, FadedOutApp> mFadedApps = new HashMap<Integer, FadedOutApp>(); + + synchronized void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) { + Log.i(TAG, "fadeOutUid() uid:" + uid); + if (!mFadedApps.containsKey(uid)) { + mFadedApps.put(uid, new FadedOutApp(uid)); + } + final FadedOutApp fa = mFadedApps.get(uid); + for (AudioPlaybackConfiguration apc : players) { + fa.addFade(apc, false /*skipRamp*/); + } + } + + synchronized void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) { + Log.i(TAG, "unfadeOutUid() uid:" + uid); + final FadedOutApp fa = mFadedApps.remove(uid); + if (fa == null) { + return; + } + fa.removeUnfadeAll(players); + } + + synchronized void forgetUid(int uid) { + //Log.v(TAG, "forget() uid:" + uid); + //mFadedApps.remove(uid); + // TODO unfade all players later in case they are reused or the app continued to play + } + + // pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED + // see {@link PlaybackActivityMonitor#playerEvent} + synchronized void checkFade(@NonNull AudioPlaybackConfiguration apc) { + if (DEBUG) { + Log.v(TAG, "checkFade() player piid:" + + apc.getPlayerInterfaceId() + " uid:" + apc.getClientUid()); + } + final FadedOutApp fa = mFadedApps.get(apc.getClientUid()); + if (fa == null) { + return; + } + fa.addFade(apc, true); + } + + /** + * Remove the player from the list of faded out players because it has been released + * @param apc the released player + */ + synchronized void removeReleased(@NonNull AudioPlaybackConfiguration apc) { + final int uid = apc.getClientUid(); + if (DEBUG) { + Log.v(TAG, "removedReleased() player piid: " + + apc.getPlayerInterfaceId() + " uid:" + uid); + } + final FadedOutApp fa = mFadedApps.get(uid); + if (fa == null) { + return; + } + fa.removeReleased(apc); + } + + synchronized void dump(PrintWriter pw) { + for (FadedOutApp da : mFadedApps.values()) { + da.dump(pw); + } + } + + //========================================================================= + /** + * Class to group players from a common app, that are faded out. + */ + private static final class FadedOutApp { + private final int mUid; + private final ArrayList<Integer> mFadedPlayers = new ArrayList<Integer>(); + + FadedOutApp(int uid) { + mUid = uid; + } + + void dump(PrintWriter pw) { + pw.print("\t uid:" + mUid + " piids:"); + for (int piid : mFadedPlayers) { + pw.print(" " + piid); + } + pw.println(""); + } + + /** + * Add this player to the list of faded out players and apply the fade + * @param apc a config that satisfies + * apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED + * @param skipRamp true if the player should be directly into the end of ramp state. + * This value would for instance be false when adding players at the start of a fade. + */ + void addFade(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + final int piid = new Integer(apc.getPlayerInterfaceId()); + if (mFadedPlayers.contains(piid)) { + if (DEBUG) { + Log.v(TAG, "player piid:" + piid + " already faded out"); + } + return; + } + try { + PlaybackActivityMonitor.sEventLogger.log( + (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(TAG)); + apc.getPlayerProxy().applyVolumeShaper( + FADEOUT_VSHAPE, + skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); + mFadedPlayers.add(piid); + } catch (Exception e) { + Log.e(TAG, "Error fading out player piid:" + piid + + " uid:" + apc.getClientUid(), e); + } + } + + void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) { + for (int piid : mFadedPlayers) { + final AudioPlaybackConfiguration apc = players.get(piid); + if (apc != null) { + try { + PlaybackActivityMonitor.sEventLogger.log( + (new AudioEventLogger.StringEvent("unfading out piid:" + + piid)).printLog(TAG)); + apc.getPlayerProxy().applyVolumeShaper( + FADEOUT_VSHAPE, + VolumeShaper.Operation.REVERSE); + } catch (Exception e) { + Log.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e); + } + } else { + // this piid was in the list of faded players, but wasn't found + if (DEBUG) { + Log.v(TAG, "Error unfading out player piid:" + piid + + ", player not found for uid " + mUid); + } + } + } + mFadedPlayers.clear(); + } + + void removeReleased(@NonNull AudioPlaybackConfiguration apc) { + mFadedPlayers.remove(new Integer(apc.getPlayerInterfaceId())); + } + } +} diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index b6d647289ed8..cc60fe1200b1 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -70,6 +70,11 @@ public class FocusRequester { */ private boolean mFocusLossWasNotified; /** + * whether this focus owner has already lost focus, but is being faded out until focus loss + * dispatch occurs. It's in "limbo" mode: has lost focus but not released yet until notified + */ + boolean mFocusLossFadeLimbo; + /** * the audio attributes associated with the focus request */ private final @NonNull AudioAttributes mAttributes; @@ -102,6 +107,7 @@ public class FocusRequester { mGrantFlags = grantFlags; mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; mFocusLossWasNotified = true; + mFocusLossFadeLimbo = false; mFocusController = ctlr; mSdkTarget = sdk; } @@ -115,6 +121,7 @@ public class FocusRequester { mFocusGainRequest = afi.getGainRequest(); mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; mFocusLossWasNotified = true; + mFocusLossFadeLimbo = false; mGrantFlags = afi.getFlags(); mSdkTarget = afi.getSdkTarget(); @@ -132,6 +139,13 @@ public class FocusRequester { return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0); } + /** + * @return true if the focus requester is scheduled to receive a focus loss + */ + boolean isInFocusLossLimbo() { + return mFocusLossFadeLimbo; + } + boolean hasSameBinder(IBinder ib) { return (mSourceRef != null) && mSourceRef.equals(ib); } @@ -231,11 +245,21 @@ public class FocusRequester { + " -- flags: " + flagsToString(mGrantFlags) + " -- loss: " + focusLossToString() + " -- notified: " + mFocusLossWasNotified + + " -- limbo" + mFocusLossFadeLimbo + " -- uid: " + mCallingUid + " -- attr: " + mAttributes + " -- sdk:" + mSdkTarget); } + /** + * Clear all references, except for instances in "loss limbo" due to the current fade out + * for which there will be an attempt to be clear after the loss has been notified + */ + void maybeRelease() { + if (!mFocusLossFadeLimbo) { + release(); + } + } void release() { final IBinder srcRef = mSourceRef; @@ -315,6 +339,7 @@ public class FocusRequester { void handleFocusGain(int focusGain) { try { mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; + mFocusLossFadeLimbo = false; mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(), AudioManager.AUDIOFOCUS_REQUEST_GRANTED); final IAudioFocusDispatcher fd = mFocusDispatcher; @@ -327,7 +352,7 @@ public class FocusRequester { fd.dispatchAudioFocusChange(focusGain, mClientId); } } - mFocusController.unduckPlayers(this); + mFocusController.restoreVShapedPlayers(this); } catch (android.os.RemoteException e) { Log.e(TAG, "Failure to signal gain of audio focus due to: ", e); } @@ -336,7 +361,7 @@ public class FocusRequester { @GuardedBy("MediaFocusControl.mAudioFocusLock") void handleFocusGainFromRequest(int focusRequestResult) { if (focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - mFocusController.unduckPlayers(this); + mFocusController.restoreVShapedPlayers(this); } } @@ -375,7 +400,7 @@ public class FocusRequester { if (handled) { if (DEBUG) { Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived) - + " to " + mClientId + ", ducking implemented by framework"); + + " to " + mClientId + ", response handled by framework"); } mFocusController.notifyExtPolicyFocusLoss_syncAf( toAudioFocusInfo(), false /* wasDispatched */); @@ -435,8 +460,27 @@ public class FocusRequester { return false; } - return mFocusController.duckPlayers(frWinner, this, forceDuck); + return mFocusController.duckPlayers(frWinner, /*loser*/ this, forceDuck); + } + + if (focusLoss == AudioManager.AUDIOFOCUS_LOSS) { + if (!MediaFocusControl.ENFORCE_FADEOUT_FOR_FOCUS_LOSS) { + return false; + } + + // candidate for fade-out before a receiving a loss + boolean playersAreFaded = mFocusController.fadeOutPlayers(frWinner, /* loser */ this); + if (playersAreFaded) { + // active players are being faded out, delay the dispatch of focus loss + // mark this instance as being faded so it's not released yet as the focus loss + // will be dispatched later, it is now in limbo mode + mFocusLossFadeLimbo = true; + mFocusController.postDelayedLossAfterFade(this, + FadeOutManager.FADE_OUT_DURATION_MS); + return true; + } } + return false; } diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index b1633b01df90..1dcfdaec44ec 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -30,7 +30,10 @@ import android.media.MediaMetrics; import android.media.audiopolicy.IAudioPolicyCallback; import android.os.Binder; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; +import android.os.Message; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; @@ -80,6 +83,12 @@ public class MediaFocusControl implements PlayerFocusEnforcer { */ static final boolean ENFORCE_MUTING_FOR_RING_OR_CALL = true; + /** + * set to true so the framework enforces fading out apps that lose audio focus in a + * non-transient way. + */ + static final boolean ENFORCE_FADEOUT_FOR_FOCUS_LOSS = true; + private final Context mContext; private final AppOpsManager mAppOps; private PlayerFocusEnforcer mFocusEnforcer; // never null @@ -98,6 +107,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { final ContentResolver cr = mContext.getContentResolver(); mMultiAudioFocusEnabled = Settings.System.getIntForUser(cr, Settings.System.MULTI_AUDIO_FOCUS_ENABLED, 0, cr.getUserId()) != 0; + initFocusThreading(); } protected void dump(PrintWriter pw) { @@ -119,8 +129,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } @Override - public void unduckPlayers(@NonNull FocusRequester winner) { - mFocusEnforcer.unduckPlayers(winner); + public void restoreVShapedPlayers(@NonNull FocusRequester winner) { + mFocusEnforcer.restoreVShapedPlayers(winner); } @Override @@ -133,6 +143,16 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusEnforcer.unmutePlayersForCall(); } + @Override + public boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser) { + return mFocusEnforcer.fadeOutPlayers(winner, loser); + } + + @Override + public void forgetUid(int uid) { + mFocusEnforcer.forgetUid(uid); + } + //========================================================================================== // AudioFocus //========================================================================================== @@ -294,7 +314,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { { //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); FocusRequester fr = mFocusStack.pop(); - fr.release(); + fr.maybeRelease(); if (notifyFocusFollowers) { abandonSource = fr.toAudioFocusInfo(); } @@ -318,7 +338,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { abandonSource = fr.toAudioFocusInfo(); } // stack entry not used anymore, clear references - fr.release(); + fr.maybeRelease(); } } } @@ -1134,4 +1154,57 @@ public class MediaFocusControl implements PlayerFocusEnforcer { pw.println("------------------------------"); } } + + //================================================================= + // Async focus events + void postDelayedLossAfterFade(FocusRequester focusLoser, long delayMs) { + if (DEBUG) { + Log.v(TAG, "postDelayedLossAfterFade loser=" + focusLoser.getPackageName()); + } + mFocusHandler.sendMessageDelayed( + mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser), + FadeOutManager.FADE_OUT_DURATION_MS); + } + //================================================================= + // Message handling + private Handler mFocusHandler; + private HandlerThread mFocusThread; + + /** + * dispatch a focus loss after an app has been faded out. Focus loser is to be released + * after dispatch as it has already left the stack + * args: + * msg.obj: the audio focus loser + * type:FocusRequester + */ + private static final int MSG_L_FOCUS_LOSS_AFTER_FADE = 1; + + private void initFocusThreading() { + mFocusThread = new HandlerThread(TAG); + mFocusThread.start(); + mFocusHandler = new Handler(mFocusThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_L_FOCUS_LOSS_AFTER_FADE: + if (DEBUG) { + Log.d(TAG, "MSG_L_FOCUS_LOSS_AFTER_FADE loser=" + + ((FocusRequester) msg.obj).getPackageName()); + } + synchronized (mAudioFocusLock) { + final FocusRequester loser = (FocusRequester) msg.obj; + if (loser.isInFocusLossLimbo()) { + loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS); + loser.release(); + mFocusEnforcer.forgetUid(loser.getClientUid()); + } + } + break; + default: + break; + } + } + }; + + } } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 8af1b5be1517..47c91e6c23dd 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -51,8 +51,9 @@ public final class PlaybackActivityMonitor public static final String TAG = "AudioService.PlaybackActivityMonitor"; - private static final boolean DEBUG = false; - private static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1; + /*package*/ static final boolean DEBUG = false; + /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1; + /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2; private static final VolumeShaper.Configuration DUCK_VSHAPE = new VolumeShaper.Configuration.Builder() @@ -298,6 +299,7 @@ public final class PlaybackActivityMonitor } if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { mDuckingManager.checkDuck(apc); + mFadingManager.checkFade(apc); } } if (change) { @@ -320,6 +322,7 @@ public final class PlaybackActivityMonitor "releasing player piid:" + piid)); mPlayers.remove(new Integer(piid)); mDuckingManager.removeReleased(apc); + mFadingManager.removeReleased(apc); checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED); change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED, AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID); @@ -442,6 +445,9 @@ public final class PlaybackActivityMonitor // ducked players pw.println("\n ducked players piids:"); mDuckingManager.dump(pw); + // faded out players + pw.println("\n faded out players piids:"); + mFadingManager.dump(pw); // players muted due to the device ringing or being in a call pw.print("\n muted player piids:"); for (int piid : mMutedPlayers) { @@ -606,10 +612,11 @@ public final class PlaybackActivityMonitor } @Override - public void unduckPlayers(@NonNull FocusRequester winner) { + public void restoreVShapedPlayers(@NonNull FocusRequester winner) { if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); } synchronized (mPlayerLock) { mDuckingManager.unduckUid(winner.getClientUid(), mPlayers); + mFadingManager.unfadeOutUid(winner.getClientUid(), mPlayers); } } @@ -678,6 +685,67 @@ public final class PlaybackActivityMonitor } } + private final FadeOutManager mFadingManager = new FadeOutManager(); + + /** + * + * @param winner the new non-transient focus owner + * @param loser the previous focus owner + * @return true if there are players being faded out + */ + @Override + public boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser) { + if (DEBUG) { + Log.v(TAG, "fadeOutPlayers: winner=" + winner.getPackageName() + + " loser=" + loser.getPackageName()); + } + boolean loserHasActivePlayers = false; + + // find which players to fade out + synchronized (mPlayerLock) { + if (mPlayers.isEmpty()) { + return false; + } + // check if this UID needs to be faded out (return false if not), and gather list of + // eligible players to fade out + final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator(); + final ArrayList<AudioPlaybackConfiguration> apcsToFadeOut = + new ArrayList<AudioPlaybackConfiguration>(); + while (apcIterator.hasNext()) { + final AudioPlaybackConfiguration apc = apcIterator.next(); + if (!winner.hasSameUid(apc.getClientUid()) + && loser.hasSameUid(apc.getClientUid()) + && apc.getPlayerState() + == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { + if (!FadeOutManager.canBeFadedOut(apc)) { + // the player is not eligible to be faded out, bail + Log.v(TAG, "not fading out player " + apc.getPlayerInterfaceId() + + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid() + + " type:" + + AudioPlaybackConfiguration.toLogFriendlyPlayerType( + apc.getPlayerType()) + + " attr:" + apc.getAudioAttributes()); + return false; + } + loserHasActivePlayers = true; + apcsToFadeOut.add(apc); + } + } + //### + //mDuckingManager.duckUid(loser.getClientUid(), apcsToFadeOut); + if (loserHasActivePlayers) { + mFadingManager.fadeOutUid(loser.getClientUid(), apcsToFadeOut); + } + } + + return loserHasActivePlayers; + } + + @Override + public void forgetUid(int uid) { + mFadingManager.forgetUid(uid); + } + //================================================================= // Track playback activity listeners @@ -964,13 +1032,15 @@ public final class PlaybackActivityMonitor } } - private static final class DuckEvent extends AudioEventLogger.Event { + private abstract static class VolumeShaperEvent extends AudioEventLogger.Event { private final int mPlayerIId; private final boolean mSkipRamp; private final int mClientUid; private final int mClientPid; - DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + abstract String getVSAction(); + + VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { mPlayerIId = apc.getPlayerInterfaceId(); mSkipRamp = skipRamp; mClientUid = apc.getClientUid(); @@ -979,12 +1049,34 @@ public final class PlaybackActivityMonitor @Override public String eventToString() { - return new StringBuilder("ducking player piid:").append(mPlayerIId) + return new StringBuilder(getVSAction()).append(" player piid:").append(mPlayerIId) .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid) .append(" skip ramp:").append(mSkipRamp).toString(); } } + static final class DuckEvent extends VolumeShaperEvent { + @Override + String getVSAction() { + return "ducking"; + } + + DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + super(apc, skipRamp); + } + } + + static final class FadeOutEvent extends VolumeShaperEvent { + @Override + String getVSAction() { + return "fading out"; + } + + FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + super(apc, skipRamp); + } + } + private static final class AudioAttrEvent extends AudioEventLogger.Event { private final int mPlayerIId; private final AudioAttributes mPlayerAttr; @@ -1000,6 +1092,6 @@ public final class PlaybackActivityMonitor } } - private static final AudioEventLogger sEventLogger = new AudioEventLogger(100, + static final AudioEventLogger sEventLogger = new AudioEventLogger(100, "playback activity as reported through PlayerBase"); } diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java index 89e7b7828b15..fb72ac282e8d 100644 --- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java +++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java @@ -31,11 +31,12 @@ public interface PlayerFocusEnforcer { boolean forceDuck); /** - * Unduck the players that had been ducked with - * {@link #duckPlayers(FocusRequester, FocusRequester, boolean)} + * Restore the initial state of any players that had had a volume ramp applied as the result + * of a duck or fade out through {@link #duckPlayers(FocusRequester, FocusRequester, boolean)} + * or {@link #fadeOutPlayers(FocusRequester, FocusRequester)} * @param winner */ - void unduckPlayers(@NonNull FocusRequester winner); + void restoreVShapedPlayers(@NonNull FocusRequester winner); /** * Mute players at the beginning of a call @@ -47,4 +48,20 @@ public interface PlayerFocusEnforcer { * Unmute players at the end of a call */ void unmutePlayersForCall(); + + /** + * Fade out whatever is still playing after the non-transient focus change + * @param winner the new non-transient focus owner + * @param loser the previous focus owner + * @return true if there were any active players for the loser that qualified for being + * faded out (because of audio attributes, or player types), and as such were faded + * out. + */ + boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser); + + /** + * Mark this UID as no longer playing a role in focus enforcement + * @param uid + */ + void forgetUid(int uid); }
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index e19745e5c578..050b28b363d2 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -33,6 +33,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -337,6 +338,168 @@ public class AuthService extends SystemService { Binder.restoreCallingIdentity(identity); } } + + @Override + public CharSequence getButtonLabel( + int userId, + String opPackageName, + @Authenticators.Types int authenticators) throws RemoteException { + + // Only allow internal clients to call getButtonLabel with a different userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + @BiometricAuthenticator.Modality final int modality = + mBiometricService.getCurrentModality( + opPackageName, userId, callingUserId, authenticators); + + final String result; + switch (getCredentialBackupModality(modality)) { + case BiometricAuthenticator.TYPE_NONE: + result = null; + break; + case BiometricAuthenticator.TYPE_CREDENTIAL: + result = getContext().getString(R.string.screen_lock_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FINGERPRINT: + result = getContext().getString(R.string.fingerprint_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FACE: + result = getContext().getString(R.string.face_app_setting_name); + break; + default: + result = getContext().getString(R.string.biometric_app_setting_name); + break; + } + + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public CharSequence getPromptMessage( + int userId, + String opPackageName, + @Authenticators.Types int authenticators) throws RemoteException { + + // Only allow internal clients to call getButtonLabel with a different userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + @BiometricAuthenticator.Modality final int modality = + mBiometricService.getCurrentModality( + opPackageName, userId, callingUserId, authenticators); + + final String result; + switch (getCredentialBackupModality(modality)) { + case BiometricAuthenticator.TYPE_NONE: + result = null; + break; + case BiometricAuthenticator.TYPE_CREDENTIAL: + result = getContext().getString( + R.string.screen_lock_dialog_default_subtitle); + break; + case BiometricAuthenticator.TYPE_FINGERPRINT: + result = getContext().getString( + R.string.fingerprint_dialog_default_subtitle); + break; + case BiometricAuthenticator.TYPE_FACE: + result = getContext().getString(R.string.face_dialog_default_subtitle); + break; + default: + result = getContext().getString(R.string.biometric_dialog_default_subtitle); + break; + } + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public CharSequence getSettingName( + int userId, + String opPackageName, + @Authenticators.Types int authenticators) throws RemoteException { + + // Only allow internal clients to call getButtonLabel with a different userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + @BiometricAuthenticator.Modality final int modality = + mBiometricService.getSupportedModalities(authenticators); + + final String result; + switch (modality) { + // Handle the case of a single supported modality. + case BiometricAuthenticator.TYPE_NONE: + result = null; + break; + case BiometricAuthenticator.TYPE_CREDENTIAL: + result = getContext().getString(R.string.screen_lock_app_setting_name); + break; + case BiometricAuthenticator.TYPE_IRIS: + result = getContext().getString(R.string.biometric_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FINGERPRINT: + result = getContext().getString(R.string.fingerprint_app_setting_name); + break; + case BiometricAuthenticator.TYPE_FACE: + result = getContext().getString(R.string.face_app_setting_name); + break; + + // Handle other possible modality combinations. + default: + if ((modality & BiometricAuthenticator.TYPE_CREDENTIAL) == 0) { + // 2+ biometric modalities are supported (but not device credential). + result = getContext().getString(R.string.biometric_app_setting_name); + } else { + @BiometricAuthenticator.Modality final int biometricModality = + modality & ~BiometricAuthenticator.TYPE_CREDENTIAL; + if (biometricModality == BiometricAuthenticator.TYPE_FINGERPRINT) { + // Only device credential and fingerprint are supported. + result = getContext().getString( + R.string.fingerprint_or_screen_lock_app_setting_name); + } else if (biometricModality == BiometricAuthenticator.TYPE_FACE) { + // Only device credential and face are supported. + result = getContext().getString( + R.string.face_or_screen_lock_app_setting_name); + } else { + // Device credential and 1+ other biometric(s) are supported. + result = getContext().getString( + R.string.biometric_or_screen_lock_app_setting_name); + } + } + break; + } + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } } public AuthService(Context context) { @@ -442,4 +605,10 @@ public class AuthService extends SystemService { return mInjector.getAppOps(getContext()).noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid, opPackageName, null /* attributionTag */, reason) == AppOpsManager.MODE_ALLOWED; } + + @BiometricAuthenticator.Modality + private static int getCredentialBackupModality(@BiometricAuthenticator.Modality int modality) { + return modality == BiometricAuthenticator.TYPE_CREDENTIAL + ? modality : (modality & ~BiometricAuthenticator.TYPE_CREDENTIAL); + } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 00a4e43f347d..a88820988ef7 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -666,14 +666,9 @@ public class BiometricService extends SystemService { throw new SecurityException("Invalid authenticator configuration"); } - final PromptInfo promptInfo = new PromptInfo(); - promptInfo.setAuthenticators(authenticators); - try { - PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, - mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, - opPackageName, - false /* checkDevicePolicyManager */); + final PreAuthInfo preAuthInfo = + createPreAuthInfo(opPackageName, userId, authenticators); return preAuthInfo.getCanAuthenticateResult(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); @@ -807,6 +802,64 @@ public class BiometricService extends SystemService { return Authenticators.EMPTY_SET; } + @Override // Binder call + public int getCurrentModality( + String opPackageName, + int userId, + int callingUserId, + @Authenticators.Types int authenticators) { + + checkInternalPermission(); + + Slog.d(TAG, "getCurrentModality: User=" + userId + + ", Caller=" + callingUserId + + ", Authenticators=" + authenticators); + + if (!Utils.isValidAuthenticatorConfig(authenticators)) { + throw new SecurityException("Invalid authenticator configuration"); + } + + try { + final PreAuthInfo preAuthInfo = + createPreAuthInfo(opPackageName, userId, authenticators); + return preAuthInfo.getPreAuthenticateStatus().first; + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + return BiometricAuthenticator.TYPE_NONE; + } + } + + @Override // Binder call + public int getSupportedModalities(@Authenticators.Types int authenticators) { + checkInternalPermission(); + + Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators); + + if (!Utils.isValidAuthenticatorConfig(authenticators)) { + throw new SecurityException("Invalid authenticator configuration"); + } + + @BiometricAuthenticator.Modality int modality = + Utils.isCredentialRequested(authenticators) + ? BiometricAuthenticator.TYPE_CREDENTIAL + : BiometricAuthenticator.TYPE_NONE; + + if (Utils.isBiometricRequested(authenticators)) { + @Authenticators.Types final int requestedStrength = + Utils.getPublicBiometricStrength(authenticators); + + // Add modalities of all biometric sensors that meet the authenticator requirements. + for (final BiometricSensor sensor : mSensors) { + @Authenticators.Types final int sensorStrength = sensor.getCurrentStrength(); + if (Utils.isAtLeastStrength(sensorStrength, requestedStrength)) { + modality |= sensor.modality; + } + } + } + + return modality; + } + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { @@ -845,6 +898,19 @@ public class BiometricService extends SystemService { "Must have USE_BIOMETRIC_INTERNAL permission"); } + @NonNull + private PreAuthInfo createPreAuthInfo( + @NonNull String opPackageName, + int userId, + @Authenticators.Types int authenticators) throws RemoteException { + + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(authenticators); + + return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, + userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */); + } + /** * Class for injecting dependencies into BiometricService. * TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger). diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 5cd0bbfa4500..d9e21a83e45a 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -153,6 +153,16 @@ public class Utils { /** * Checks if any of the publicly defined strengths are set. * + * @param authenticators composed of one or more values from {@link Authenticators} + * @return true if biometric authentication is allowed. + */ + static boolean isBiometricRequested(@Authenticators.Types int authenticators) { + return getPublicBiometricStrength(authenticators) != 0; + } + + /** + * Checks if any of the publicly defined strengths are set. + * * @param promptInfo should be first processed by * {@link #combineAuthenticatorBundles(PromptInfo)} * @return true if biometric authentication is allowed. diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java index d7e08e462777..7a846f5b14a5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.GenerateChallengeClient; */ public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISession> { private static final String TAG = "FaceGenerateChallengeClient"; - private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes FaceGenerateChallengeClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, @@ -43,7 +42,7 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISessio @Override protected void startHalOperation() { try { - getFreshDaemon().generateChallenge(mSequentialId, CHALLENGE_TIMEOUT_SEC); + getFreshDaemon().generateChallenge(mSequentialId); } catch (RemoteException e) { Slog.e(TAG, "Unable to generateChallenge", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java index afa5bd22e081..b4c9b290cd06 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java @@ -45,7 +45,7 @@ public class TestHal extends IFace.Stub { return new ISession.Stub() { @Override - public void generateChallenge(int cookie, int timeoutSec) throws RemoteException { + public void generateChallenge(int cookie) throws RemoteException { Slog.w(TAG, "generateChallenge, cookie: " + cookie); cb.onChallengeGenerated(0L); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java index 402886b0e92c..3c9cceddb5fd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.GenerateChallengeClient; */ class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISession> { private static final String TAG = "FingerprintGenerateChallengeClient"; - private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes FingerprintGenerateChallengeClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @@ -45,7 +44,7 @@ class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISessio @Override protected void startHalOperation() { try { - getFreshDaemon().generateChallenge(mSequentialId, CHALLENGE_TIMEOUT_SEC); + getFreshDaemon().generateChallenge(mSequentialId); } catch (RemoteException e) { Slog.e(TAG, "Unable to generateChallenge", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java index 9db2fcfd1b5b..8547a689dfb1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java @@ -45,7 +45,7 @@ public class TestHal extends IFingerprint.Stub { return new ISession.Stub() { @Override - public void generateChallenge(int cookie, int timeoutSec) throws RemoteException { + public void generateChallenge(int cookie) throws RemoteException { Slog.w(TAG, "generateChallenge, cookie: " + cookie); cb.onChallengeGenerated(0L); } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 2de709ebe71d..088249e81171 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -52,6 +52,7 @@ import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; @@ -59,6 +60,7 @@ import android.util.SparseArray; import android.view.autofill.AutofillManagerInternal; import android.widget.Toast; +import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; @@ -163,6 +165,10 @@ public class ClipboardService extends SystemService { private static final boolean IS_EMULATOR = SystemProperties.getBoolean("ro.kernel.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 final ActivityManagerInternal mAmInternal; private final IUriGrantsManager mUgm; private final UriGrantsManagerInternal mUgmInternal; @@ -176,8 +182,14 @@ public class ClipboardService extends SystemService { private HostClipboardMonitor mHostClipboardMonitor = null; private Thread mHostMonitorThread = null; + @GuardedBy("mLock") private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>(); + @GuardedBy("mLock") + private boolean mShowAccessNotifications = DEFAULT_SHOW_ACCESS_NOTIFICATIONS; + + private final Object mLock = new Object(); + /** * Instantiates the clipboard. */ @@ -204,7 +216,7 @@ public class ClipboardService extends SystemService { new ClipData("host clipboard", new String[]{"text/plain"}, new ClipData.Item(contents)); - synchronized(mClipboards) { + synchronized (mLock) { setPrimaryClipInternal(getClipboard(0), clip, android.os.Process.SYSTEM_UID, null); } @@ -213,6 +225,10 @@ public class ClipboardService extends SystemService { mHostMonitorThread = new Thread(mHostClipboardMonitor); mHostMonitorThread.start(); } + + updateConfig(); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD, + getContext().getMainExecutor(), properties -> updateConfig()); } @Override @@ -222,11 +238,18 @@ public class ClipboardService extends SystemService { @Override public void onUserStopped(@NonNull TargetUser user) { - synchronized (mClipboards) { + synchronized (mLock) { mClipboards.remove(user.getUserIdentifier()); } } + private void updateConfig() { + synchronized (mLock) { + mShowAccessNotifications = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD, + PROPERTY_SHOW_ACCESS_NOTIFICATIONS, DEFAULT_SHOW_ACCESS_NOTIFICATIONS); + } + } + private class ListenerInfo { final int mUid; final String mPackageName; @@ -472,7 +495,7 @@ public class ClipboardService extends SystemService { }; private PerUserClipboard getClipboard(@UserIdInt int userId) { - synchronized (mClipboards) { + synchronized (mLock) { PerUserClipboard puc = mClipboards.get(userId); if (puc == null) { puc = new PerUserClipboard(userId); @@ -849,9 +872,10 @@ public class ClipboardService extends SystemService { if (clipboard.primaryClip == null) { return; } - if (Settings.Global.getInt(getContext().getContentResolver(), - "clipboard_access_toast_enabled", 1) == 0) { - return; + synchronized (mLock) { + if (!mShowAccessNotifications) { + return; + } } // Don't notify if the app accessing the clipboard is the same as the current owner. if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) { @@ -880,8 +904,8 @@ public class ClipboardService extends SystemService { CharSequence sourceAppLabel = null; if (clipboard.mPrimaryClipPackage != null) { try { - sourceAppLabel = mPm.getApplicationLabel( - mPm.getApplicationInfo(clipboard.mPrimaryClipPackage, 0)); + sourceAppLabel = mPm.getApplicationLabel(mPm.getApplicationInfoAsUser( + clipboard.mPrimaryClipPackage, 0, userId)); } catch (PackageManager.NameNotFoundException e) { // leave label as null } @@ -889,7 +913,7 @@ public class ClipboardService extends SystemService { try { CharSequence callingAppLabel = mPm.getApplicationLabel( - mPm.getApplicationInfo(callingPackage, 0)); + mPm.getApplicationInfoAsUser(callingPackage, 0, userId)); String message; if (sourceAppLabel != null) { message = callingAppLabel + " pasted from " + sourceAppLabel; diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index df83df9a73fb..5cf478a3ef1f 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -80,6 +80,15 @@ public final class CompatChange extends CompatibilityChangeInfo { } /** + * @param change an object generated by services/core/xsd/platform-compat-config.xsd + */ + public CompatChange(Change change) { + this(change.getId(), change.getName(), change.getEnableAfterTargetSdk(), + change.getEnableSinceTargetSdk(), change.getDisabled(), change.getLoggingOnly(), + change.getDescription(), change.getOverridable()); + } + + /** * @param changeId Unique ID for the change. See {@link android.compat.Compatibility}. * @param name Short descriptive name. * @param enableAfterTargetSdk {@code targetSdkVersion} restriction. See {@link EnabledAfter}; @@ -93,15 +102,10 @@ public final class CompatChange extends CompatibilityChangeInfo { boolean overridable) { super(changeId, name, enableAfterTargetSdk, enableSinceTargetSdk, disabled, loggingOnly, description, overridable); - } - /** - * @param change an object generated by services/core/xsd/platform-compat-config.xsd - */ - public CompatChange(Change change) { - super(change.getId(), change.getName(), change.getEnableAfterTargetSdk(), - change.getEnableSinceTargetSdk(), change.getDisabled(), change.getLoggingOnly(), - change.getDescription(), change.getOverridable()); + // Initialize override maps. + mEvaluatedOverrides = new HashMap<>(); + mRawOverrides = new HashMap<>(); } void registerListener(ChangeListener listener) { @@ -127,18 +131,13 @@ public final class CompatChange extends CompatibilityChangeInfo { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mEvaluatedOverrides == null) { - mEvaluatedOverrides = new HashMap<>(); - } mEvaluatedOverrides.put(pname, enabled); notifyListener(pname); } private void removePackageOverrideInternal(String pname) { - if (mEvaluatedOverrides != null) { - if (mEvaluatedOverrides.remove(pname) != null) { - notifyListener(pname); - } + if (mEvaluatedOverrides.remove(pname) != null) { + notifyListener(pname); } } @@ -157,9 +156,6 @@ public final class CompatChange extends CompatibilityChangeInfo { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mRawOverrides == null) { - mRawOverrides = new HashMap<>(); - } mRawOverrides.put(packageName, override); recheckOverride(packageName, allowedState, context); } @@ -212,7 +208,7 @@ public final class CompatChange extends CompatibilityChangeInfo { } boolean hasPackageOverride(String pname) { - return mRawOverrides != null && mRawOverrides.containsKey(pname); + return mRawOverrides.containsKey(pname); } /** * Remove any package override for the given package name, restoring the default behaviour. @@ -223,7 +219,7 @@ public final class CompatChange extends CompatibilityChangeInfo { */ boolean removePackageOverride(String pname, OverrideAllowedState allowedState, Context context) { - if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) { + if (mRawOverrides.remove(pname) != null) { recheckOverride(pname, allowedState, context); return true; } @@ -241,7 +237,7 @@ public final class CompatChange extends CompatibilityChangeInfo { if (app == null) { return defaultValue(); } - if (mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(app.packageName)) { + if (mEvaluatedOverrides.containsKey(app.packageName)) { return mEvaluatedOverrides.get(app.packageName); } if (getDisabled()) { @@ -289,7 +285,7 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return true if there is such override */ private boolean hasOverride(String packageName) { - return mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(packageName); + return mEvaluatedOverrides.containsKey(packageName); } /** @@ -298,20 +294,15 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return true if there is such a deferred override */ private boolean hasRawOverride(String packageName) { - return mRawOverrides != null && mRawOverrides.containsKey(packageName); + return mRawOverrides.containsKey(packageName); } - void loadOverrides(ChangeOverrides changeOverrides) { - if (mRawOverrides == null) { - mRawOverrides = new HashMap<>(); - } + void clearOverrides() { mRawOverrides.clear(); - - if (mEvaluatedOverrides == null) { - mEvaluatedOverrides = new HashMap<>(); - } mEvaluatedOverrides.clear(); + } + void loadOverrides(ChangeOverrides changeOverrides) { // Load deferred overrides for backwards compatibility if (changeOverrides.getDeferred() != null) { for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { @@ -345,34 +336,30 @@ public final class CompatChange extends CompatibilityChangeInfo { } ChangeOverrides saveOverrides() { - if (mRawOverrides == null || mRawOverrides.isEmpty()) { + if (mRawOverrides.isEmpty()) { return null; } ChangeOverrides changeOverrides = new ChangeOverrides(); changeOverrides.setChangeId(getId()); ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw(); List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue(); - if (mRawOverrides != null) { - for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) { - RawOverrideValue override = new RawOverrideValue(); - override.setPackageName(entry.getKey()); - override.setMinVersionCode(entry.getValue().getMinVersionCode()); - override.setMaxVersionCode(entry.getValue().getMaxVersionCode()); - override.setEnabled(entry.getValue().getEnabled()); - rawList.add(override); - } + for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) { + RawOverrideValue override = new RawOverrideValue(); + override.setPackageName(entry.getKey()); + override.setMinVersionCode(entry.getValue().getMinVersionCode()); + override.setMaxVersionCode(entry.getValue().getMaxVersionCode()); + override.setEnabled(entry.getValue().getEnabled()); + rawList.add(override); } changeOverrides.setRaw(rawOverrides); ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated(); List<OverrideValue> validatedList = validatedOverrides.getOverrideValue(); - if (mEvaluatedOverrides != null) { - for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) { - OverrideValue override = new OverrideValue(); - override.setPackageName(entry.getKey()); - override.setEnabled(entry.getValue()); - validatedList.add(override); - } + for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) { + OverrideValue override = new OverrideValue(); + override.setPackageName(entry.getKey()); + override.setEnabled(entry.getValue()); + validatedList.add(override); } changeOverrides.setValidated(validatedOverrides); return changeOverrides; @@ -394,10 +381,10 @@ public final class CompatChange extends CompatibilityChangeInfo { if (getLoggingOnly()) { sb.append("; loggingOnly"); } - if (mEvaluatedOverrides != null && mEvaluatedOverrides.size() > 0) { + if (!mEvaluatedOverrides.isEmpty()) { sb.append("; packageOverrides=").append(mEvaluatedOverrides); } - if (mRawOverrides != null && mRawOverrides.size() > 0) { + if (!mRawOverrides.isEmpty()) { sb.append("; rawOverrides=").append(mRawOverrides); } if (getOverridable()) { diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 66a652053857..2c053b421904 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -67,6 +67,7 @@ final class CompatConfig { private static final String TAG = "CompatConfig"; private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat"; + private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat"; private static final String OVERRIDES_FILE = "compat_framework_overrides.xml"; @GuardedBy("mChanges") @@ -94,8 +95,7 @@ final class CompatConfig { config.initConfigFromLib(Environment.buildPath( apex.apexDirectory, "etc", "compatconfig")); } - File overridesFile = new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE); - config.initOverrides(overridesFile); + config.initOverrides(); config.invalidateCache(); return config; } @@ -525,10 +525,34 @@ final class CompatConfig { } } - void initOverrides(File overridesFile) { + private void initOverrides() { + initOverrides(new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE), + new File(STATIC_OVERRIDES_PRODUCT_DIR, OVERRIDES_FILE)); + } + + @VisibleForTesting + void initOverrides(File dynamicOverridesFile, File staticOverridesFile) { + // Clear overrides from all changes before loading. + synchronized (mChanges) { + for (int i = 0; i < mChanges.size(); ++i) { + mChanges.valueAt(i).clearOverrides(); + } + } + + loadOverrides(staticOverridesFile); + + mOverridesFile = dynamicOverridesFile; + loadOverrides(dynamicOverridesFile); + + if (staticOverridesFile.exists()) { + // Only save overrides if there is a static overrides file. + saveOverrides(); + } + } + + private void loadOverrides(File overridesFile) { if (!overridesFile.exists()) { - mOverridesFile = overridesFile; - // There have not been any overrides added yet. + // Overrides file doesn't exist. return; } @@ -548,7 +572,6 @@ final class CompatConfig { Slog.w(TAG, "Error processing " + overridesFile + " " + e.toString()); return; } - mOverridesFile = overridesFile; } /** diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index 43d9ade67a11..4f6b5301e56f 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -19,6 +19,8 @@ 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.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; @@ -147,17 +149,18 @@ public class DnsManager { } public static class PrivateDnsValidationUpdate { - final public int netId; - final public InetAddress ipAddress; - final public String hostname; - final public boolean validated; + public final int netId; + public final InetAddress ipAddress; + public final String hostname; + // Refer to IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_*. + public final int validationResult; public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress, - String hostname, boolean validated) { + String hostname, int validationResult) { this.netId = netId; this.ipAddress = ipAddress; this.hostname = hostname; - this.validated = validated; + this.validationResult = validationResult; } } @@ -216,10 +219,13 @@ public class DnsManager { if (!mValidationMap.containsKey(p)) { return; } - if (update.validated) { + if (update.validationResult == VALIDATION_RESULT_SUCCESS) { mValidationMap.put(p, ValidationStatus.SUCCEEDED); - } else { + } else if (update.validationResult == VALIDATION_RESULT_FAILURE) { mValidationMap.put(p, ValidationStatus.FAILED); + } else { + Log.e(TAG, "Unknown private dns validation operation=" + + update.validationResult); } } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 34d9ccc15dba..7b20ded19205 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -57,8 +57,8 @@ import android.util.Log; import android.util.Pair; import com.android.internal.R; -import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; +import com.android.net.module.util.HexDump; import com.android.net.module.util.IpUtils; import java.io.FileDescriptor; diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 641287f0f435..fa80b25f9026 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -29,14 +29,12 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.RouteInfo; -import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetworkStackConstants; -import com.android.server.net.BaseNetworkObserver; import java.net.Inet6Address; import java.util.Objects; @@ -48,7 +46,7 @@ import java.util.Objects; * * @hide */ -public class Nat464Xlat extends BaseNetworkObserver { +public class Nat464Xlat { private static final String TAG = Nat464Xlat.class.getSimpleName(); // This must match the interface prefix in clatd.c. @@ -70,7 +68,6 @@ public class Nat464Xlat extends BaseNetworkObserver { private final IDnsResolver mDnsResolver; private final INetd mNetd; - private final INetworkManagementService mNMService; // The network we're running on, and its type. private final NetworkAgentInfo mNetwork; @@ -99,11 +96,9 @@ public class Nat464Xlat extends BaseNetworkObserver { private boolean mPrefixDiscoveryRunning; - public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver, - INetworkManagementService nmService) { + public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver) { mDnsResolver = dnsResolver; mNetd = netd; - mNMService = nmService; mNetwork = nai; } @@ -174,13 +169,6 @@ public class Nat464Xlat extends BaseNetworkObserver { * and set internal state. */ private void enterStartingState(String baseIface) { - try { - mNMService.registerObserver(this); - } catch (RemoteException e) { - Log.e(TAG, "Can't register iface observer for clat on " + mNetwork.toShortString()); - return; - } - mNat64PrefixInUse = selectNat64Prefix(); String addrStr = null; try { @@ -216,11 +204,6 @@ public class Nat464Xlat extends BaseNetworkObserver { * Unregister as a base observer for the stacked interface, and clear internal state. */ private void leaveStartedState() { - try { - mNMService.unregisterObserver(this); - } catch (RemoteException | IllegalStateException e) { - Log.e(TAG, "Error unregistering clatd observer on " + mBaseIface + ": " + e); - } mNat64PrefixInUse = null; mIface = null; mBaseIface = null; @@ -507,12 +490,10 @@ public class Nat464Xlat extends BaseNetworkObserver { stop(); } - @Override public void interfaceLinkStateChanged(String iface, boolean up) { mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); }); } - @Override public void interfaceRemoved(String iface) { mNetwork.handler().post(() -> handleInterfaceRemoved(iface)); } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 4cf527415d7e..1d0e11569c80 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -35,7 +35,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMonitorManager; import android.net.NetworkRequest; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.QosCallbackException; import android.net.QosFilter; import android.net.QosFilterParcelable; @@ -43,7 +43,6 @@ import android.net.QosSession; import android.net.TcpKeepalivePacketData; import android.os.Handler; import android.os.IBinder; -import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.SystemClock; import android.telephony.data.EpsBearerQosSessionAttributes; @@ -341,8 +340,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, int score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, - IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber, - int creatorUid, QosCallbackTracker qosCallbackTracker) { + IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid, + QosCallbackTracker qosCallbackTracker) { Objects.requireNonNull(net); Objects.requireNonNull(info); Objects.requireNonNull(lp); @@ -356,7 +355,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { linkProperties = lp; networkCapabilities = nc; mScore = score; - clatd = new Nat464Xlat(this, netd, dnsResolver, nms); + clatd = new Nat464Xlat(this, netd, dnsResolver); mConnService = connService; mContext = context; mHandler = handler; @@ -891,15 +890,18 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mScore = score; } - public NetworkState getNetworkState() { + /** + * Return a {@link NetworkStateSnapshot} for this network. + */ + @NonNull + public NetworkStateSnapshot getNetworkStateSnapshot() { synchronized (this) { // Network objects are outwardly immutable so there is no point in duplicating. // Duplicating also precludes sharing socket factories and connection pools. final String subscriberId = (networkAgentConfig != null) ? networkAgentConfig.subscriberId : null; - return new NetworkState(new NetworkInfo(networkInfo), - new LinkProperties(linkProperties), - new NetworkCapabilities(networkCapabilities), network, subscriberId); + return new NetworkStateSnapshot(network, new NetworkCapabilities(networkCapabilities), + new LinkProperties(linkProperties), subscriberId, networkInfo.getType()); } } diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java index 816bf2be0d69..0f5400d0f8e6 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java @@ -27,7 +27,7 @@ import android.net.QosSession; import android.os.IBinder; import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; -import android.util.Slog; +import android.util.Log; import java.util.Objects; @@ -175,18 +175,14 @@ class QosCallbackAgentConnection implements IBinder.DeathRecipient { } private static void log(@NonNull final String msg) { - Slog.d(TAG, msg); + Log.d(TAG, msg); } private static void logw(@NonNull final String msg) { - Slog.w(TAG, msg); + Log.w(TAG, msg); } private static void loge(@NonNull final String msg, final Throwable t) { - Slog.e(TAG, msg, t); - } - - private static void logwtf(@NonNull final String msg) { - Slog.wtf(TAG, msg); + Log.e(TAG, msg, t); } } diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java index 7ef315c469ae..8bda5323e4f8 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -29,7 +29,7 @@ import android.os.IBinder; import android.telephony.data.EpsBearerQosSessionAttributes; import android.util.Log; -import com.android.internal.util.CollectionUtils; +import com.android.net.module.util.CollectionUtils; import com.android.server.ConnectivityService; import java.util.ArrayList; @@ -156,12 +156,13 @@ public class QosCallbackTracker { private void handleUnregisterCallback(@NonNull final IBinder binder, final boolean sendToNetworkAgent) { - final QosCallbackAgentConnection agentConnection = - CollectionUtils.find(mConnections, c -> c.getBinder().equals(binder)); - if (agentConnection == null) { - logw("handleUnregisterCallback: agentConnection is null"); + final int connIndex = + CollectionUtils.indexOf(mConnections, c -> c.getBinder().equals(binder)); + if (connIndex < 0) { + logw("handleUnregisterCallback: no matching agentConnection"); return; } + final QosCallbackAgentConnection agentConnection = mConnections.get(connIndex); if (DBG) { log("handleUnregisterCallback: unregister " @@ -226,10 +227,10 @@ public class QosCallbackTracker { * @param network the network that was released */ public void handleNetworkReleased(@Nullable final Network network) { - final List<QosCallbackAgentConnection> connections = - CollectionUtils.filter(mConnections, ac -> ac.getNetwork().equals(network)); - - for (final QosCallbackAgentConnection agentConnection : connections) { + // Iterate in reverse order as agent connections will be removed when unregistering + for (int i = mConnections.size() - 1; i >= 0; i--) { + final QosCallbackAgentConnection agentConnection = mConnections.get(i); + if (!agentConnection.getNetwork().equals(network)) continue; agentConnection.sendEventQosCallbackError( QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); @@ -247,15 +248,14 @@ public class QosCallbackTracker { @NonNull final String logPrefix, @NonNull final AgentConnectionAction action) { mConnectivityServiceHandler.post(() -> { - final QosCallbackAgentConnection ac = - CollectionUtils.find(mConnections, + final int acIndex = CollectionUtils.indexOf(mConnections, c -> c.getAgentCallbackId() == qosCallbackId); - if (ac == null) { + if (acIndex == -1) { loge(logPrefix + ": " + qosCallbackId + " missing callback id"); return; } - action.execute(ac); + action.execute(mConnections.get(acIndex)); }); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 67f495a455fb..2e61ae1b3483 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -101,7 +101,12 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; -import android.security.KeyStore; +import android.security.KeyStore2; +import android.security.keystore.AndroidKeyStoreProvider; +import android.security.keystore.KeyProperties; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyPermission; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; @@ -132,6 +137,12 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -157,6 +168,7 @@ public class Vpn { private static final String TAG = "Vpn"; private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:"; private static final boolean LOGD = true; + private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore"; // Length of time (in milliseconds) that an app hosting an always-on VPN is placed on // the device idle allowlist during service launch and VPN bootstrap. @@ -216,6 +228,13 @@ public class Vpn { private final Ikev2SessionCreator mIkev2SessionCreator; private final UserManager mUserManager; + private final VpnProfileStore mVpnProfileStore; + + @VisibleForTesting + VpnProfileStore getVpnProfileStore() { + return mVpnProfileStore; + } + /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This * only applies to {@link VpnService} connections. @@ -393,24 +412,25 @@ public class Vpn { } public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd, - @UserIdInt int userId, @NonNull KeyStore keyStore) { - this(looper, context, new Dependencies(), netService, netd, userId, keyStore, + @UserIdInt int userId, VpnProfileStore vpnProfileStore) { + this(looper, context, new Dependencies(), netService, netd, userId, vpnProfileStore, new SystemServices(context), new Ikev2SessionCreator()); } @VisibleForTesting public Vpn(Looper looper, Context context, Dependencies deps, INetworkManagementService netService, INetd netd, @UserIdInt int userId, - @NonNull KeyStore keyStore) { - this(looper, context, deps, netService, netd, userId, keyStore, + VpnProfileStore vpnProfileStore) { + this(looper, context, deps, netService, netd, userId, vpnProfileStore, new SystemServices(context), new Ikev2SessionCreator()); } @VisibleForTesting protected Vpn(Looper looper, Context context, Dependencies deps, INetworkManagementService netService, INetd netd, - int userId, @NonNull KeyStore keyStore, SystemServices systemServices, + int userId, VpnProfileStore vpnProfileStore, SystemServices systemServices, Ikev2SessionCreator ikev2SessionCreator) { + mVpnProfileStore = vpnProfileStore; mContext = context; mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); @@ -446,7 +466,7 @@ public class Vpn { mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); - loadAlwaysOnPackage(keyStore); + loadAlwaysOnPackage(); } /** @@ -567,11 +587,9 @@ public class Vpn { * </ul> * * @param packageName the canonical package name of the VPN app - * @param keyStore the keystore instance to use for checking if the app has a Platform VPN - * profile installed. * @return {@code true} if and only if the VPN app exists and supports always-on mode */ - public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) { + public boolean isAlwaysOnPackageSupported(String packageName) { enforceSettingsPermission(); if (packageName == null) { @@ -580,7 +598,7 @@ public class Vpn { final long oldId = Binder.clearCallingIdentity(); try { - if (getVpnProfilePrivileged(packageName, keyStore) != null) { + if (getVpnProfilePrivileged(packageName) != null) { return true; } } finally { @@ -632,17 +650,15 @@ public class Vpn { * @param packageName the package to designate as always-on VPN supplier. * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. * @param lockdownAllowlist packages to be allowed from lockdown. - * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s) * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ public synchronized boolean setAlwaysOnPackage( @Nullable String packageName, boolean lockdown, - @Nullable List<String> lockdownAllowlist, - @NonNull KeyStore keyStore) { + @Nullable List<String> lockdownAllowlist) { enforceControlPermissionOrInternalCaller(); - if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist, keyStore)) { + if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) { saveAlwaysOnPackage(); return true; } @@ -659,13 +675,12 @@ public class Vpn { * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. * @param lockdownAllowlist packages to be allowed to bypass lockdown. This is only used if * {@code lockdown} is {@code true}. Packages must not contain commas. - * @param keyStore the system keystore instance to check for profiles * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ @GuardedBy("this") private boolean setAlwaysOnPackageInternal( @Nullable String packageName, boolean lockdown, - @Nullable List<String> lockdownAllowlist, @NonNull KeyStore keyStore) { + @Nullable List<String> lockdownAllowlist) { if (VpnConfig.LEGACY_VPN.equals(packageName)) { Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on."); return false; @@ -684,7 +699,7 @@ public class Vpn { final VpnProfile profile; final long oldId = Binder.clearCallingIdentity(); try { - profile = getVpnProfilePrivileged(packageName, keyStore); + profile = getVpnProfilePrivileged(packageName); } finally { Binder.restoreCallingIdentity(oldId); } @@ -759,7 +774,7 @@ public class Vpn { /** Load the always-on package and lockdown config from Settings. */ @GuardedBy("this") - private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) { + private void loadAlwaysOnPackage() { final long token = Binder.clearCallingIdentity(); try { final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser( @@ -771,7 +786,7 @@ public class Vpn { final List<String> allowedPackages = TextUtils.isEmpty(allowlistString) ? Collections.emptyList() : Arrays.asList(allowlistString.split(",")); setAlwaysOnPackageInternal( - alwaysOnPackage, alwaysOnLockdown, allowedPackages, keyStore); + alwaysOnPackage, alwaysOnLockdown, allowedPackages); } finally { Binder.restoreCallingIdentity(token); } @@ -780,11 +795,10 @@ public class Vpn { /** * Starts the currently selected always-on VPN * - * @param keyStore the keyStore instance for looking up PlatformVpnProfile(s) * @return {@code true} if the service was started, the service was already connected, or there * was no always-on VPN to start. {@code false} otherwise. */ - public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) { + public boolean startAlwaysOnVpn() { final String alwaysOnPackage; synchronized (this) { alwaysOnPackage = getAlwaysOnPackage(); @@ -793,8 +807,8 @@ public class Vpn { return true; } // Remove always-on VPN if it's not supported. - if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) { - setAlwaysOnPackage(null, false, null, keyStore); + if (!isAlwaysOnPackageSupported(alwaysOnPackage)) { + setAlwaysOnPackage(null, false, null); return false; } // Skip if the service is already established. This isn't bulletproof: it's not bound @@ -808,10 +822,9 @@ public class Vpn { final long oldId = Binder.clearCallingIdentity(); try { // Prefer VPN profiles, if any exist. - VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore); + VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage); if (profile != null) { - startVpnProfilePrivileged(profile, alwaysOnPackage, - null /* keyStore for private key retrieval - unneeded */); + startVpnProfilePrivileged(profile, alwaysOnPackage); // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was // correctly parsed, and the VPN has started running in a different thread. The only @@ -2013,27 +2026,83 @@ public class Vpn { * secondary thread to perform connection work, returning quickly. * * Should only be called to respond to Binder requests as this enforces caller permission. Use - * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, Network, LinkProperties)} to skip the + * {@link #startLegacyVpnPrivileged(VpnProfile, Network, LinkProperties)} to skip the * permission check only when the caller is trusted (or the call is initiated by the system). */ - public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying, + public void startLegacyVpn(VpnProfile profile, @Nullable Network underlying, LinkProperties egress) { enforceControlPermission(); final long token = Binder.clearCallingIdentity(); try { - startLegacyVpnPrivileged(profile, keyStore, underlying, egress); + startLegacyVpnPrivileged(profile, underlying, egress); } finally { Binder.restoreCallingIdentity(token); } } + private String makeKeystoreEngineGrantString(String alias) { + if (alias == null) { + return null; + } + // If Keystore 2.0 is not enabled the legacy private key prefix is used. + if (!AndroidKeyStoreProvider.isKeystore2Enabled()) { + return Credentials.USER_PRIVATE_KEY + alias; + } + final KeyStore2 keystore2 = KeyStore2.getInstance(); + + KeyDescriptor key = new KeyDescriptor(); + key.domain = Domain.APP; + key.nspace = KeyProperties.NAMESPACE_APPLICATION; + key.alias = alias; + key.blob = null; + + final int grantAccessVector = KeyPermission.USE | KeyPermission.GET_INFO; + + try { + // The native vpn daemon is running as VPN_UID. This tells Keystore 2.0 + // to allow a process running with this UID to access the key designated by + // the KeyDescriptor `key`. `grant` returns a new KeyDescriptor with a grant + // identifier. This identifier needs to be communicated to the vpn daemon. + key = keystore2.grant(key, android.os.Process.VPN_UID, grantAccessVector); + } catch (android.security.KeyStoreException e) { + Log.e(TAG, "Failed to get grant for keystore key.", e); + throw new IllegalStateException("Failed to get grant for keystore key.", e); + } + + // Turn the grant identifier into a string as understood by the keystore boringssl engine + // in system/security/keystore-engine. + return KeyStore2.makeKeystoreEngineGrantString(key.nspace); + } + + private String getCaCertificateFromKeystoreAsPem(@NonNull KeyStore keystore, + @NonNull String alias) + throws KeyStoreException, IOException, CertificateEncodingException { + if (keystore.isCertificateEntry(alias)) { + final Certificate cert = keystore.getCertificate(alias); + if (cert == null) return null; + return new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8); + } else { + final Certificate[] certs = keystore.getCertificateChain(alias); + // If there is none or one entry it means there is no CA entry associated with this + // alias. + if (certs == null || certs.length <= 1) { + return null; + } + // If this is not a (pure) certificate entry, then there is a user certificate which + // will be included at the beginning of the certificate chain. But the caller of this + // function does not expect this certificate to be included, so we cut it off. + return new String(Credentials.convertToPem( + Arrays.copyOfRange(certs, 1, certs.length)), StandardCharsets.UTF_8); + } + } + /** - * Like {@link #startLegacyVpn(VpnProfile, KeyStore, Network, LinkProperties)}, but does not + * Like {@link #startLegacyVpn(VpnProfile, Network, LinkProperties)}, but does not * check permissions under the assumption that the caller is the system. * * Callers are responsible for checking permissions if needed. */ - public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore, + public void startLegacyVpnPrivileged(VpnProfile profile, @Nullable Network underlying, @NonNull LinkProperties egress) { UserInfo user = mUserManager.getUserInfo(mUserId); if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, @@ -2050,18 +2119,27 @@ public class Vpn { String userCert = ""; String caCert = ""; String serverCert = ""; - if (!profile.ipsecUserCert.isEmpty()) { - privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert; - byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert); - userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); - } - if (!profile.ipsecCaCert.isEmpty()) { - byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert); - caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); - } - if (!profile.ipsecServerCert.isEmpty()) { - byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert); - serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); + + try { + final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER); + keystore.load(null); + if (!profile.ipsecUserCert.isEmpty()) { + privateKey = profile.ipsecUserCert; + final Certificate cert = keystore.getCertificate(profile.ipsecUserCert); + userCert = (cert == null) ? null + : new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8); + } + if (!profile.ipsecCaCert.isEmpty()) { + caCert = getCaCertificateFromKeystoreAsPem(keystore, profile.ipsecCaCert); + } + if (!profile.ipsecServerCert.isEmpty()) { + final Certificate cert = keystore.getCertificate(profile.ipsecServerCert); + serverCert = (cert == null) ? null + : new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8); + } + } catch (CertificateException | KeyStoreException | IOException + | NoSuchAlgorithmException e) { + throw new IllegalStateException("Failed to load credentials from AndroidKeyStore", e); } if (userCert == null || caCert == null || serverCert == null) { throw new IllegalStateException("Cannot load credentials"); @@ -2082,7 +2160,7 @@ public class Vpn { // Start VPN profile profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS); - startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); + startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN); return; case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // Ikev2VpnProfiles expect a base64-encoded preshared key. @@ -2091,7 +2169,7 @@ public class Vpn { // Start VPN profile profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS); - startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); + startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN); return; case VpnProfile.TYPE_L2TP_IPSEC_PSK: racoon = new String[] { @@ -2101,8 +2179,8 @@ public class Vpn { break; case VpnProfile.TYPE_L2TP_IPSEC_RSA: racoon = new String[] { - iface, profile.server, "udprsa", privateKey, userCert, - caCert, serverCert, "1701", + iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey), + userCert, caCert, serverCert, "1701", }; break; case VpnProfile.TYPE_IPSEC_XAUTH_PSK: @@ -2113,8 +2191,8 @@ public class Vpn { break; case VpnProfile.TYPE_IPSEC_XAUTH_RSA: racoon = new String[] { - iface, profile.server, "xauthrsa", privateKey, userCert, - caCert, serverCert, profile.username, profile.password, "", gateway, + iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey), + userCert, caCert, serverCert, profile.username, profile.password, "", gateway, }; break; case VpnProfile.TYPE_IPSEC_HYBRID_RSA: @@ -3049,14 +3127,12 @@ public class Vpn { * * @param packageName the package name of the app provisioning this profile * @param profile the profile to be stored and provisioned - * @param keyStore the System keystore instance to save VPN profiles * @returns whether or not the app has already been granted user consent */ public synchronized boolean provisionVpnProfile( - @NonNull String packageName, @NonNull VpnProfile profile, @NonNull KeyStore keyStore) { + @NonNull String packageName, @NonNull VpnProfile profile) { checkNotNull(packageName, "No package name provided"); checkNotNull(profile, "No profile provided"); - checkNotNull(keyStore, "KeyStore missing"); verifyCallingUidAndPackage(packageName); enforceNotRestrictedUser(); @@ -3075,11 +3151,9 @@ public class Vpn { // Permissions checked during startVpnProfile() Binder.withCleanCallingIdentity( () -> { - keyStore.put( + getVpnProfileStore().put( getProfileNameForPackage(packageName), - encodedProfile, - Process.SYSTEM_UID, - 0 /* flags */); + encodedProfile); }); // TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop. @@ -3097,12 +3171,10 @@ public class Vpn { * Deletes an app-provisioned VPN profile. * * @param packageName the package name of the app provisioning this profile - * @param keyStore the System keystore instance to save VPN profiles */ public synchronized void deleteVpnProfile( - @NonNull String packageName, @NonNull KeyStore keyStore) { + @NonNull String packageName) { checkNotNull(packageName, "No package name provided"); - checkNotNull(keyStore, "KeyStore missing"); verifyCallingUidAndPackage(packageName); enforceNotRestrictedUser(); @@ -3114,13 +3186,13 @@ public class Vpn { if (isCurrentIkev2VpnLocked(packageName)) { if (mAlwaysOn) { // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN). - setAlwaysOnPackage(null, false, null, keyStore); + setAlwaysOnPackage(null, false, null); } else { prepareInternal(VpnConfig.LEGACY_VPN); } } - keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID); + getVpnProfileStore().remove(getProfileNameForPackage(packageName)); }); } @@ -3132,13 +3204,13 @@ public class Vpn { */ @VisibleForTesting @Nullable - VpnProfile getVpnProfilePrivileged(@NonNull String packageName, @NonNull KeyStore keyStore) { + VpnProfile getVpnProfilePrivileged(@NonNull String packageName) { if (!mDeps.isCallerSystem()) { Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID "); return null; } - final byte[] encoded = keyStore.get(getProfileNameForPackage(packageName)); + final byte[] encoded = getVpnProfileStore().get(getProfileNameForPackage(packageName)); if (encoded == null) return null; return VpnProfile.decode("" /* Key unused */, encoded); @@ -3152,12 +3224,10 @@ public class Vpn { * will not match during appop checks. * * @param packageName the package name of the app provisioning this profile - * @param keyStore the System keystore instance to retrieve VPN profiles */ public synchronized void startVpnProfile( - @NonNull String packageName, @NonNull KeyStore keyStore) { + @NonNull String packageName) { checkNotNull(packageName, "No package name provided"); - checkNotNull(keyStore, "KeyStore missing"); enforceNotRestrictedUser(); @@ -3168,18 +3238,17 @@ public class Vpn { Binder.withCleanCallingIdentity( () -> { - final VpnProfile profile = getVpnProfilePrivileged(packageName, keyStore); + final VpnProfile profile = getVpnProfilePrivileged(packageName); if (profile == null) { throw new IllegalArgumentException("No profile found for " + packageName); } - startVpnProfilePrivileged(profile, packageName, - null /* keyStore for private key retrieval - unneeded */); + startVpnProfilePrivileged(profile, packageName); }); } private synchronized void startVpnProfilePrivileged( - @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) { + @NonNull VpnProfile profile, @NonNull String packageName) { // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(), // by the Setting app via startLegacyVpn(), or by ConnectivityService via // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the @@ -3210,7 +3279,7 @@ public class Vpn { case VpnProfile.TYPE_IKEV2_IPSEC_PSK: case VpnProfile.TYPE_IKEV2_IPSEC_RSA: mVpnRunner = - new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore)); + new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile)); mVpnRunner.start(); break; default: @@ -3218,7 +3287,7 @@ public class Vpn { Log.d(TAG, "Unknown VPN profile type: " + profile.type); break; } - } catch (IOException | GeneralSecurityException e) { + } catch (GeneralSecurityException e) { // Reset mConfig mConfig = null; diff --git a/services/core/java/com/android/server/connectivity/VpnProfileStore.java b/services/core/java/com/android/server/connectivity/VpnProfileStore.java new file mode 100644 index 000000000000..2f8aebf07e49 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/VpnProfileStore.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.server.connectivity; + +import android.annotation.NonNull; +import android.security.LegacyVpnProfileStore; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Mockable indirection to the actual profile store. + * @hide + */ +public class VpnProfileStore { + /** + * Stores the profile under the alias in the profile database. Existing profiles by the + * same name will be replaced. + * @param alias The name of the profile + * @param profile The profile. + * @return true if the profile was successfully added. False otherwise. + * @hide + */ + @VisibleForTesting + public boolean put(@NonNull String alias, @NonNull byte[] profile) { + return LegacyVpnProfileStore.put(alias, profile); + } + + /** + * Retrieves a profile by the name alias from the profile database. + * @param alias Name of the profile to retrieve. + * @return The unstructured blob, that is the profile that was stored using + * LegacyVpnProfileStore#put or with + * android.security.Keystore.put(Credentials.VPN + alias). + * Returns null if no profile was found. + * @hide + */ + @VisibleForTesting + public byte[] get(@NonNull String alias) { + return LegacyVpnProfileStore.get(alias); + } + + /** + * Removes a profile by the name alias from the profile database. + * @param alias Name of the profile to be removed. + * @return True if a profile was removed. False if no such profile was found. + * @hide + */ + @VisibleForTesting + public boolean remove(@NonNull String alias) { + return LegacyVpnProfileStore.remove(alias); + } + + /** + * Lists the vpn profiles stored in the database. + * @return An array of strings representing the aliases stored in the profile database. + * The return value may be empty but never null. + * @hide + */ + @VisibleForTesting + public @NonNull String[] list(@NonNull String prefix) { + return LegacyVpnProfileStore.list(prefix); + } +} diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 835b4688dd4e..658d27f43313 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -425,7 +425,13 @@ public final class DeviceStateManagerService extends SystemService { if (!mRequestRecords.isEmpty()) { final OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1); - topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE); + if (topRequest.mRequestedState.getIdentifier() == mCommittedState.getIdentifier()) { + // The top request could have come in while the service was awaiting callback + // from the policy. In that case we only set it to active if it matches the + // current committed state, otherwise it will be set to active when its + // requested state is committed. + topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE); + } } mPendingState = Optional.empty(); @@ -563,10 +569,13 @@ public final class DeviceStateManagerService extends SystemService { new OverrideRequestRecord(processRecord, token, deviceState.get(), flags); mRequestRecords.add(request); processRecord.mRequestRecords.put(request.mToken, request); - // We don't set the status of the new request to ACTIVE here as it will be set in - // commitPendingState(). - updatePendingStateLocked(); + final boolean updatedPendingState = updatePendingStateLocked(); + if (!updatedPendingState && !mPendingState.isPresent()) { + // We don't set the status of the new request to ACTIVE if the request updated the + // pending state as it will be set in commitPendingState(). + request.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE, true /* markDirty */); + } } notifyRequestsOfStatusChangeIfNeeded(); diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index d4556ed5f9fa..1acd5d097525 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -16,17 +16,26 @@ package com.android.server.display; -import android.content.Context; import android.hardware.devicestate.DeviceStateManager; -import android.text.TextUtils; +import android.os.Environment; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; import android.view.DisplayAddress; +import com.android.server.display.config.layout.Layouts; +import com.android.server.display.config.layout.XmlParser; import com.android.server.display.layout.Layout; -import java.util.Arrays; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.datatype.DatatypeConfigurationException; /** * Mapping from device states into {@link Layout}s. This allows us to map device @@ -39,15 +48,14 @@ class DeviceStateToLayoutMap { public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE; - // TODO - b/168208162 - Remove these when we check in static definitions for layouts - public static final int STATE_FOLDED = 100; - public static final int STATE_UNFOLDED = 101; + private static final String CONFIG_FILE_PATH = + "etc/displayconfig/display_layout_configuration.xml"; private final SparseArray<Layout> mLayoutMap = new SparseArray<>(); - DeviceStateToLayoutMap(Context context) { - mLayoutMap.append(STATE_DEFAULT, new Layout()); - loadFoldedDisplayConfig(context); + DeviceStateToLayoutMap() { + loadLayoutsFromConfig(); + createLayout(STATE_DEFAULT); } public void dumpLocked(IndentingPrintWriter ipw) { @@ -68,7 +76,7 @@ class DeviceStateToLayoutMap { return layout; } - private Layout create(int state) { + private Layout createLayout(int state) { if (mLayoutMap.contains(state)) { Slog.e(TAG, "Attempted to create a second layout for state " + state); return null; @@ -79,43 +87,37 @@ class DeviceStateToLayoutMap { return layout; } - private void loadFoldedDisplayConfig(Context context) { - final String[] strDisplayIds = context.getResources().getStringArray( - com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds); - - if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0]) - || TextUtils.isEmpty(strDisplayIds[1])) { - Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds) - + "]"); - return; - } + /** + * Reads display-layout-configuration files to get the layouts to use for this device. + */ + private void loadLayoutsFromConfig() { + final File configFile = Environment.buildPath( + Environment.getVendorDirectory(), CONFIG_FILE_PATH); - final long[] displayIds; - try { - displayIds = new long[] { - Long.parseLong(strDisplayIds[0]), - Long.parseLong(strDisplayIds[1]) - }; - } catch (NumberFormatException nfe) { - Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds)); + if (!configFile.exists()) { return; } - final int[] foldedDeviceStates = context.getResources().getIntArray( - com.android.internal.R.array.config_foldedDeviceStates); - // Only add folded states if folded state config is not empty - if (foldedDeviceStates.length == 0) { - return; + Slog.i(TAG, "Loading display layouts from " + configFile); + try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { + final Layouts layouts = XmlParser.read(in); + if (layouts == null) { + Slog.i(TAG, "Display layout config not found: " + configFile); + return; + } + for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) { + final int state = l.getState().intValue(); + final Layout layout = createLayout(state); + for (com.android.server.display.config.layout.Display d: l.getDisplay()) { + layout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()), + d.getIsDefault(), + d.getEnabled()); + } + } + } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { + Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: " + + configFile, e); } - - // Create the folded state layout - final Layout foldedLayout = create(STATE_FOLDED); - foldedLayout.createDisplayLocked( - DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/); - - // Create the unfolded state layout - final Layout unfoldedLayout = create(STATE_UNFOLDED); - unfoldedLayout.createDisplayLocked( - DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/); } } diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java index 663883ab2825..2dcd5ccaf557 100644 --- a/services/core/java/com/android/server/display/DisplayGroup.java +++ b/services/core/java/com/android/server/display/DisplayGroup.java @@ -30,6 +30,8 @@ public class DisplayGroup { private final List<LogicalDisplay> mDisplays = new ArrayList<>(); private final int mGroupId; + private int mChangeCount; + DisplayGroup(int groupId) { mGroupId = groupId; } @@ -45,11 +47,16 @@ public class DisplayGroup { * @param display the {@link LogicalDisplay} to add to the Group */ void addDisplayLocked(LogicalDisplay display) { - if (!mDisplays.contains(display)) { + if (!containsLocked(display)) { + mChangeCount++; mDisplays.add(display); } } + boolean containsLocked(LogicalDisplay display) { + return mDisplays.contains(display); + } + /** * Removes the provided {@code display} from the Group. * @@ -57,6 +64,7 @@ public class DisplayGroup { * @return {@code true} if the {@code display} was removed; otherwise {@code false} */ boolean removeDisplayLocked(LogicalDisplay display) { + mChangeCount++; return mDisplays.remove(display); } @@ -65,6 +73,11 @@ public class DisplayGroup { return mDisplays.isEmpty(); } + /** Returns a count of the changes made to this display group. */ + int getChangeCountLocked() { + return mChangeCount; + } + /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */ int getSizeLocked() { return mDisplays.size(); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 52149ee3a4dd..174d4b2fe00d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -423,7 +423,7 @@ public final class DisplayManagerService extends SystemService { mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); - mLogicalDisplayMapper = new LogicalDisplayMapper(context, mDisplayDeviceRepo, + mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, new LogicalDisplayListener()); mDisplayModeDirector = new DisplayModeDirector(context, mHandler); mBrightnessSynchronizer = new BrightnessSynchronizer(mContext); @@ -485,13 +485,13 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { long timeout = SystemClock.uptimeMillis() + mInjector.getDefaultDisplayDelayTimeout(); - while (mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY) == null + while (mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY) == null || mVirtualDisplayAdapter == null) { long delay = timeout - SystemClock.uptimeMillis(); if (delay <= 0) { throw new RuntimeException("Timeout waiting for default display " + "to be initialized. DefaultDisplay=" - + mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY) + + mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY) + ", mVirtualDisplayAdapter=" + mVirtualDisplayAdapter); } if (DEBUG) { @@ -549,7 +549,7 @@ public final class DisplayManagerService extends SystemService { mSystemReady = true; // Just in case the top inset changed before the system was ready. At this point, any // relevant configuration should be in place. - recordTopInsetLocked(mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY)); + recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)); updateSettingsLocked(); } @@ -617,7 +617,7 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting void setDisplayInfoOverrideFromWindowManagerInternal(int displayId, DisplayInfo info) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) { handleLogicalDisplayChangedLocked(display); @@ -632,7 +632,7 @@ public final class DisplayManagerService extends SystemService { */ private void getNonOverrideDisplayInfoInternal(int displayId, DisplayInfo outInfo) { synchronized (mSyncRoot) { - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { display.getNonOverrideDisplayInfoLocked(outInfo); } @@ -691,8 +691,8 @@ public final class DisplayManagerService extends SystemService { mDisplayStates.setValueAt(index, state); mDisplayBrightnesses.setValueAt(index, brightnessState); - runnable = updateDisplayStateLocked( - mLogicalDisplayMapper.getLocked(displayId).getPrimaryDisplayDeviceLocked()); + runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId) + .getPrimaryDisplayDeviceLocked()); } // Setting the display power state can take hundreds of milliseconds @@ -803,9 +803,9 @@ public final class DisplayManagerService extends SystemService { private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { - DisplayInfo info = + final DisplayInfo info = getDisplayInfoForFrameRateOverride(display.getFrameRateOverrides(), display.getDisplayInfoLocked(), callingUid); if (info.hasAccess(callingUid) @@ -952,7 +952,7 @@ public final class DisplayManagerService extends SystemService { private void requestColorModeInternal(int displayId, int colorMode) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null && display.getRequestedColorModeLocked() != colorMode) { display.setRequestedColorModeLocked(colorMode); @@ -989,7 +989,7 @@ public final class DisplayManagerService extends SystemService { mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); - LogicalDisplay display = mLogicalDisplayMapper.getLocked(device); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); if (display != null) { return display.getDisplayIdLocked(); } @@ -1178,7 +1178,10 @@ public final class DisplayManagerService extends SystemService { private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) { final int displayId = display.getDisplayIdLocked(); - mDisplayPowerControllers.removeReturnOld(displayId).stop(); + final DisplayPowerController dpc = mDisplayPowerControllers.removeReturnOld(displayId); + if (dpc != null) { + dpc.stop(); + } mDisplayStates.delete(displayId); mDisplayBrightnesses.delete(displayId); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); @@ -1200,10 +1203,7 @@ public final class DisplayManagerService extends SystemService { // by the display power controller (if known). DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { - // TODO - b/170498827 The rules regarding what display state to apply to each - // display will depend on the configuration/mapping of logical displays. - // Clean up LogicalDisplay.isEnabled() mechanism once this is fixed. - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); final int state; final int displayId = display.getDisplayIdLocked(); @@ -1364,7 +1364,7 @@ public final class DisplayManagerService extends SystemService { float requestedRefreshRate, int requestedModeId, boolean preferMinimalPostProcessing, boolean inTraversal) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display == null) { return; } @@ -1406,7 +1406,7 @@ public final class DisplayManagerService extends SystemService { private void setDisplayOffsetsInternal(int displayId, int x, int y) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display == null) { return; } @@ -1424,7 +1424,7 @@ public final class DisplayManagerService extends SystemService { private void setDisplayScalingDisabledInternal(int displayId, boolean disable) { synchronized (mSyncRoot) { - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display == null) { return; } @@ -1460,7 +1460,7 @@ public final class DisplayManagerService extends SystemService { @Nullable private IBinder getDisplayToken(int displayId) { synchronized (mSyncRoot) { - final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); if (device != null) { @@ -1478,7 +1478,7 @@ public final class DisplayManagerService extends SystemService { if (token == null) { return null; } - final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId); if (logicalDisplay == null) { return null; } @@ -1611,15 +1611,16 @@ public final class DisplayManagerService extends SystemService { // Find the logical display that the display device is showing. // Certain displays only ever show their own content. - LogicalDisplay display = mLogicalDisplayMapper.getLocked(device); + LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); if (!ownContent) { if (display != null && !display.hasContentLocked()) { // If the display does not have any content of its own, then // automatically mirror the requested logical display contents if possible. - display = mLogicalDisplayMapper.getLocked(device.getDisplayIdToMirrorLocked()); + display = mLogicalDisplayMapper.getDisplayLocked( + device.getDisplayIdToMirrorLocked()); } if (display == null) { - display = mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY); + display = mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY); } } @@ -1896,9 +1897,9 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting DisplayDeviceInfo getDisplayDeviceInfoInternal(int displayId) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { - DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); + final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); return displayDevice.getDisplayDeviceInfoLocked(); } return null; @@ -1908,9 +1909,9 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting int getDisplayIdToMirrorInternal(int displayId) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { - DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); + final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); return displayDevice.getDisplayIdToMirrorLocked(); } return Display.INVALID_DISPLAY; @@ -1992,7 +1993,8 @@ public final class DisplayManagerService extends SystemService { ArraySet<Integer> uids; synchronized (mSyncRoot) { int displayId = msg.arg1; - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = + mLogicalDisplayMapper.getDisplayLocked(displayId); uids = display.getPendingFrameRateOverrideUids(); display.clearPendingFrameRateOverrideUids(); } @@ -2586,7 +2588,7 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public boolean isMinimalPostProcessingRequested(int displayId) { synchronized (mSyncRoot) { - return mLogicalDisplayMapper.getLocked(displayId) + return mLogicalDisplayMapper.getDisplayLocked(displayId) .getRequestedMinimalPostProcessingLocked(); } } @@ -2831,7 +2833,7 @@ public final class DisplayManagerService extends SystemService { @Override public Point getDisplayPosition(int displayId) { synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId); + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display != null) { return display.getDisplayPosition(); } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index dce6bd849953..645ca7ac33e0 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -31,7 +31,6 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -80,6 +79,8 @@ public class DisplayModeDirector { // specific display. private static final int GLOBAL_ID = -1; + private static final int INVALID_DISPLAY_MODE_ID = -1; + // The tolerance within which we consider something approximately equals. private static final float FLOAT_TOLERANCE = 0.01f; @@ -322,12 +323,30 @@ public class DisplayModeDirector { appRequestSummary.maxRefreshRate)); } - // If the application requests a given mode with preferredModeId function, it will be - // stored as baseModeId. - int baseModeId = defaultMode.getModeId(); - if (availableModes.length > 0) { + int baseModeId = INVALID_DISPLAY_MODE_ID; + + // Select the default mode if available. This is important because SurfaceFlinger + // can do only seamless switches by default. Some devices (e.g. TV) don't support + // seamless switching so the mode we select here won't be changed. + for (int availableMode : availableModes) { + if (availableMode == defaultMode.getModeId()) { + baseModeId = defaultMode.getModeId(); + break; + } + } + + // If the application requests a display mode by setting + // LayoutParams.preferredDisplayModeId, it will be the only available mode and it'll + // be stored as baseModeId. + if (baseModeId == INVALID_DISPLAY_MODE_ID && availableModes.length > 0) { baseModeId = availableModes[0]; } + + if (baseModeId == INVALID_DISPLAY_MODE_ID) { + throw new IllegalStateException("Can't select a base display mode for display " + + displayId + ". The votes are " + mVotesByDisplay.valueAt(displayId)); + } + if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) { Display.Mode baseMode = null; for (Display.Mode mode : modes) { @@ -351,6 +370,7 @@ public class DisplayModeDirector { boolean allowGroupSwitching = mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; + return new DesiredDisplayModeSpecs(baseModeId, allowGroupSwitching, new RefreshRateRange( diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 3709963b7caa..a0d93656b7f6 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -767,6 +767,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { } private void setDisplayBrightness(float brightness) { + // Ensure brightnessState is valid, before processing and sending to + // surface control + if (Float.isNaN(brightness)) { + return; + } + if (DEBUG) { Slog.d(TAG, "setDisplayBrightness(" + "id=" + physicalDisplayId diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 20b133ce4d0a..d9570c710b15 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -17,11 +17,11 @@ package com.android.server.display; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManagerInternal; import android.util.ArraySet; -import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayEventReceiver; @@ -64,12 +64,14 @@ import java.util.Objects; */ final class LogicalDisplay { private static final String TAG = "LogicalDisplay"; - private final DisplayInfo mBaseDisplayInfo = new DisplayInfo(); // The layer stack we use when the display has been blanked to prevent any // of its content from appearing. private static final int BLANK_LAYER_STACK = -1; + private static final DisplayInfo EMPTY_DISPLAY_INFO = new DisplayInfo(); + + private final DisplayInfo mBaseDisplayInfo = new DisplayInfo(); private final int mDisplayId; private final int mLayerStack; @@ -297,7 +299,7 @@ final class LogicalDisplay { // Check whether logical display has become invalid. if (!deviceRepo.containsLocked(mPrimaryDisplayDevice)) { - mPrimaryDisplayDevice = null; + setPrimaryDisplayDeviceLocked(null); return; } @@ -684,18 +686,28 @@ final class LogicalDisplay { * @param targetDisplay The display with which to swap display-devices. * @return {@code true} if the displays were swapped, {@code false} otherwise. */ - public boolean swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) { - final DisplayDevice targetDevice = targetDisplay.getPrimaryDisplayDeviceLocked(); - if (mPrimaryDisplayDevice == null || targetDevice == null) { - Slog.e(TAG, "Missing display device during swap: " + mPrimaryDisplayDevice + " , " - + targetDevice); - return false; - } + public void swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) { + final DisplayDevice oldTargetDevice = + targetDisplay.setPrimaryDisplayDeviceLocked(mPrimaryDisplayDevice); + setPrimaryDisplayDeviceLocked(oldTargetDevice); + } + + /** + * Sets the primary display device to the specified device. + * + * @param device The new device to set. + * @return The previously set display device. + */ + public DisplayDevice setPrimaryDisplayDeviceLocked(@Nullable DisplayDevice device) { + final DisplayDevice old = mPrimaryDisplayDevice; + mPrimaryDisplayDevice = device; - final DisplayDevice tmpDevice = mPrimaryDisplayDevice; - mPrimaryDisplayDevice = targetDisplay.mPrimaryDisplayDevice; - targetDisplay.mPrimaryDisplayDevice = tmpDevice; - return true; + // Reset all our display info data + mPrimaryDisplayDeviceInfo = null; + mBaseDisplayInfo.copyFrom(EMPTY_DISPLAY_INFO); + mInfo.set(null); + + return old; } /** @@ -718,8 +730,8 @@ final class LogicalDisplay { public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); - pw.println("mLayerStack=" + mLayerStack); pw.println("mIsEnabled=" + mIsEnabled); + pw.println("mLayerStack=" + mLayerStack); pw.println("mHasContent=" + mHasContent); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); pw.println("mRequestedColorMode=" + mRequestedColorMode); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index a3ff534e336e..d6826be248df 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -16,14 +16,16 @@ package com.android.server.display; -import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; import android.os.SystemProperties; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.view.Display; -import android.view.DisplayEventReceiver; +import android.view.DisplayAddress; import android.view.DisplayInfo; import com.android.server.display.layout.Layout; @@ -74,49 +76,79 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final boolean mSingleDisplayDemoMode; /** - * List of all logical displays indexed by logical display id. + * Map of all logical displays indexed by logical display id. * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache. * TODO: multi-display - Move the aforementioned comment? */ private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>(); - /** A mapping from logical display id to display group. */ - private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>(); + /** Map of all display groups indexed by display group id. */ + private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>(); private final DisplayDeviceRepository mDisplayDeviceRepo; private final DeviceStateToLayoutMap mDeviceStateToLayoutMap; private final Listener mListener; - private final int[] mFoldedDeviceStates; + + /** + * Has an entry for every logical display that the rest of the system has been notified about. + * Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it + * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. + */ + private final SparseBooleanArray mUpdatedLogicalDisplays = new SparseBooleanArray(); + + /** + * Keeps track of all the display groups that we already told other people about. IOW, if a + * display group is in this array, then we *must* send change and remove notifications for it + * because other components know about them. Also, what this array stores is a change counter + * for each group, so we know if the group itself has changes since we last sent out a + * notification. See {@link DisplayGroup#getChangeCountLocked}. + */ + private final SparseIntArray mUpdatedDisplayGroups = new SparseIntArray(); + + /** + * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out. + */ + private final SparseIntArray mLogicalDisplaysToUpdate = new SparseIntArray(); + + /** + * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out. + */ + private final SparseIntArray mDisplayGroupsToUpdate = new SparseIntArray(); private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private Layout mCurrentLayout = null; - private boolean mIsFolded = false; + private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; - LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) { + LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener) { mDisplayDeviceRepo = repo; mListener = listener; mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); mDisplayDeviceRepo.addListener(this); - - mFoldedDeviceStates = context.getResources().getIntArray( - com.android.internal.R.array.config_foldedDeviceStates); - - mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context); + mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); } @Override public void onDisplayDeviceEventLocked(DisplayDevice device, int event) { switch (event) { case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED: + if (DEBUG) { + Slog.d(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked()); + } handleDisplayDeviceAddedLocked(device); break; case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED: + if (DEBUG) { + Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked()); + } updateLogicalDisplaysLocked(); break; case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED: + if (DEBUG) { + Slog.d(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked()); + } updateLogicalDisplaysLocked(); break; } @@ -127,11 +159,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mListener.onTraversalRequested(); } - public LogicalDisplay getLocked(int displayId) { + public LogicalDisplay getDisplayLocked(int displayId) { return mLogicalDisplays.get(displayId); } - public LogicalDisplay getLocked(DisplayDevice device) { + public LogicalDisplay getDisplayLocked(DisplayDevice device) { final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { LogicalDisplay display = mLogicalDisplays.valueAt(i); @@ -166,16 +198,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } } - public DisplayGroup getDisplayGroupLocked(int groupId) { - final int size = mDisplayIdToGroupMap.size(); + public int getDisplayGroupIdFromDisplayIdLocked(int displayId) { + final LogicalDisplay display = getDisplayLocked(displayId); + if (display == null) { + return Display.INVALID_DISPLAY_GROUP; + } + + final int size = mDisplayGroups.size(); for (int i = 0; i < size; i++) { - final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i); - if (displayGroup.getGroupId() == groupId) { - return displayGroup; + final DisplayGroup displayGroup = mDisplayGroups.valueAt(i); + if (displayGroup.containsLocked(display)) { + return mDisplayGroups.keyAt(i); } } - return null; + return Display.INVALID_DISPLAY_GROUP; + } + + public DisplayGroup getDisplayGroupLocked(int groupId) { + return mDisplayGroups.get(groupId); } public void dumpLocked(PrintWriter pw) { @@ -203,229 +244,334 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } void setDeviceStateLocked(int state) { - boolean folded = false; - for (int i = 0; i < mFoldedDeviceStates.length; i++) { - if (state == mFoldedDeviceStates[i]) { - folded = true; - break; - } + if (state != mDeviceState) { + resetLayoutLocked(); + mDeviceState = state; + applyLayoutLocked(); + updateLogicalDisplaysLocked(); } - setDeviceFoldedLocked(folded); } - void setDeviceFoldedLocked(boolean isFolded) { - mIsFolded = isFolded; + private void handleDisplayDeviceAddedLocked(DisplayDevice device) { + DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); + // Internal Displays need to have additional initialization. + // TODO: b/168208162 - This initializes a default dynamic display layout for INTERNAL + // devices, which will eventually just be a fallback in case no static layout definitions + // exist or cannot be loaded. + if (deviceInfo.type == Display.TYPE_INTERNAL) { + initializeInternalDisplayDeviceLocked(device); + } - // Until we have fully functioning state mapping, use hardcoded states based on isFolded - final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED - : DeviceStateToLayoutMap.STATE_UNFOLDED; + // Create a logical display for the new display device + LogicalDisplay display = createNewLogicalDisplayLocked( + device, Layout.assignDisplayIdLocked(false /*isDefault*/)); - if (DEBUG) { - Slog.d(TAG, "New device state: " + state); - } + applyLayoutLocked(); + updateLogicalDisplaysLocked(); + } - final Layout layout = mDeviceStateToLayoutMap.get(state); - if (layout == null) { - return; - } - final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY); - if (displayLayout == null) { - return; - } - final DisplayDevice newDefaultDevice = - mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress()); - if (newDefaultDevice == null) { - return; - } + /** + * Updates the rest of the display system once all the changes are applied for display + * devices and logical displays. The includes releasing invalid/empty LogicalDisplays, + * creating/adjusting/removing DisplayGroups, and notifying the rest of the system of the + * relevant changes. + */ + private void updateLogicalDisplaysLocked() { + // Go through all the displays and figure out if they need to be updated. + // Loops in reverse so that displays can be removed during the loop without affecting the + // rest of the loop. + for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) { + final int displayId = mLogicalDisplays.keyAt(i); + LogicalDisplay display = mLogicalDisplays.valueAt(i); - final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); - mCurrentLayout = layout; + mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); + display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo); - // If we're already set up accurately, return early - if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) { - return; - } + display.updateLocked(mDisplayDeviceRepo); + final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked(); + final boolean wasPreviouslyUpdated = mUpdatedLogicalDisplays.get(displayId); - // We need to swap the default display's display-device with the one that is supposed - // to be the default in the new layout. - final LogicalDisplay displayToSwap = getLocked(newDefaultDevice); - if (displayToSwap == null) { - Slog.w(TAG, "Canceling display swap - unexpected empty second display for: " - + newDefaultDevice); - return; - } - defaultDisplay.swapDisplaysLocked(displayToSwap); + // The display is no longer valid and needs to be removed. + if (!display.isValidLocked()) { + mUpdatedLogicalDisplays.delete(displayId); - // We ensure that the non-default Display is always forced to be off. This was likely - // already done in a previous iteration, but we do it with each swap in case something in - // the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example. - defaultDisplay.setEnabled(true); - displayToSwap.setEnabled(false); + // Remove from group + final DisplayGroup displayGroup = getDisplayGroupLocked( + getDisplayGroupIdFromDisplayIdLocked(displayId)); + if (displayGroup != null) { + displayGroup.removeDisplayLocked(display); + } - // Update the world - updateLogicalDisplaysLocked(); - } + if (wasPreviouslyUpdated) { + // The display isn't actually removed from our internal data structures until + // after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}. + Slog.i(TAG, "Removing display: " + displayId); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED); + } else { + // This display never left this class, safe to remove without notification + mLogicalDisplays.removeAt(i); + } + continue; + + // The display is new. + } else if (!wasPreviouslyUpdated) { + Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo); + assignDisplayGroupLocked(display); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED); + + // Underlying displays device has changed to a different one. + } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) { + // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case + assignDisplayGroupLocked(display); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED); + + // Something about the display device has changed. + } else if (!mTempDisplayInfo.equals(newDisplayInfo)) { + // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case + assignDisplayGroupLocked(display); + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED); + + // Display frame rate overrides changed. + } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) { + mLogicalDisplaysToUpdate.put( + displayId, LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); - private void handleDisplayDeviceAddedLocked(DisplayDevice device) { - DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); - boolean isDefault = (deviceInfo.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; - if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) { - Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo); - isDefault = false; + // Non-override display values changed. + } else { + // While application shouldn't know nor care about the non-overridden info, we + // still need to let WindowManager know so it can update its own internal state for + // things like display cutouts. + display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo); + if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) { + mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED); + } + } + + mUpdatedLogicalDisplays.put(displayId, true); } - if (!isDefault && mSingleDisplayDemoMode) { - Slog.i(TAG, "Not creating a logical display for a secondary display " - + " because single display demo mode is enabled: " + deviceInfo); - return; + // Go through the groups and do the same thing. We do this after displays since group + // information can change in the previous loop. + // Loops in reverse so that groups can be removed during the loop without affecting the + // rest of the loop. + for (int i = mDisplayGroups.size() - 1; i >= 0; i--) { + final int groupId = mDisplayGroups.keyAt(i); + final DisplayGroup group = mDisplayGroups.valueAt(i); + final boolean wasPreviouslyUpdated = mUpdatedDisplayGroups.indexOfKey(groupId) < 0; + final int changeCount = group.getChangeCountLocked(); + + if (group.isEmptyLocked()) { + mUpdatedDisplayGroups.delete(groupId); + if (wasPreviouslyUpdated) { + mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_REMOVED); + } + continue; + } else if (!wasPreviouslyUpdated) { + mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_ADDED); + } else if (mUpdatedDisplayGroups.get(groupId) != changeCount) { + mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_CHANGED); + } + mUpdatedDisplayGroups.put(groupId, changeCount); } - final int displayId = Layout.assignDisplayIdLocked(isDefault); - final int layerStack = assignLayerStackLocked(displayId); + // Send the display and display group updates in order by message type. This is important + // to ensure that addition and removal notifications happen in the right order. + sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED); + sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED); + sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED); + + mLogicalDisplaysToUpdate.clear(); + mDisplayGroupsToUpdate.clear(); + } - final DisplayGroup displayGroup; - final boolean addNewDisplayGroup = - isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0; - if (addNewDisplayGroup) { - final int groupId = assignDisplayGroupIdLocked(isDefault); - displayGroup = new DisplayGroup(groupId); - } else { - displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY); - } + /** + * Send the specified message for all relevant displays in the specified display-to-message map. + */ + private void sendUpdatesForDisplaysLocked(int msg) { + for (int i = mLogicalDisplaysToUpdate.size() - 1; i >= 0; --i) { + final int currMsg = mLogicalDisplaysToUpdate.valueAt(i); + if (currMsg != msg) { + continue; + } - LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); - display.updateDisplayGroupIdLocked(displayGroup.getGroupId()); - display.updateLocked(mDisplayDeviceRepo); - if (!display.isValidLocked()) { - // This should never happen currently. - Slog.w(TAG, "Ignoring display device because the logical display " - + "created from it was not considered valid: " + deviceInfo); - return; + final int id = mLogicalDisplaysToUpdate.keyAt(i); + mListener.onLogicalDisplayEventLocked(getDisplayLocked(id), msg); + if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) { + // We wait until we sent the EVENT_REMOVED event before actually removing the + // display. + mLogicalDisplays.delete(id); + } } + } + + /** + * Send the specified message for all relevant display groups in the specified message map. + */ + private void sendUpdatesForGroupsLocked(int msg) { + for (int i = mDisplayGroupsToUpdate.size() - 1; i >= 0; --i) { + final int currMsg = mDisplayGroupsToUpdate.valueAt(i); + if (currMsg != msg) { + continue; + } - // For foldable devices, we start the internal non-default displays as disabled. - // TODO - b/168208162 - this will be removed when we recalculate the layout with each - // display-device addition. - if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL - && !isDefault) { - display.setEnabled(false); + final int id = mDisplayGroupsToUpdate.keyAt(i); + mListener.onDisplayGroupEventLocked(id, msg); + if (msg == DISPLAY_GROUP_EVENT_REMOVED) { + // We wait until we sent the EVENT_REMOVED event before actually removing the + // group. + mDisplayGroups.delete(id); + } } + } - mLogicalDisplays.put(displayId, display); - displayGroup.addDisplayLocked(display); - mDisplayIdToGroupMap.append(displayId, displayGroup); + private void assignDisplayGroupLocked(LogicalDisplay display) { + final int displayId = display.getDisplayIdLocked(); - if (addNewDisplayGroup) { - // Group added events happen before Logical Display added events. - mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED); - } + // Get current display group data + int groupId = getDisplayGroupIdFromDisplayIdLocked(displayId); + final DisplayGroup oldGroup = getDisplayGroupLocked(groupId); - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED); + // Get the new display group if a change is needed + final DisplayInfo info = display.getDisplayInfoLocked(); + final boolean needsOwnDisplayGroup = (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0; + final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP; + if (groupId == Display.INVALID_DISPLAY_GROUP + || hasOwnDisplayGroup != needsOwnDisplayGroup) { + groupId = assignDisplayGroupIdLocked(needsOwnDisplayGroup); + } - if (!addNewDisplayGroup) { - // Group changed events happen after Logical Display added events. - mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED); + // Create a new group if needed + DisplayGroup newGroup = getDisplayGroupLocked(groupId); + if (newGroup == null) { + newGroup = new DisplayGroup(groupId); + mDisplayGroups.append(groupId, newGroup); + } + if (oldGroup != newGroup) { + if (oldGroup != null) { + oldGroup.removeDisplayLocked(display); + } + newGroup.addDisplayLocked(display); + display.updateDisplayGroupIdLocked(groupId); + Slog.i(TAG, "Setting new display group " + groupId + " for display " + + displayId + ", from previous group: " + + (oldGroup != null ? oldGroup.getGroupId() : "null")); } + } - if (DEBUG) { - Slog.d(TAG, "New Display added: " + display); + /** + * Resets the current layout in preparation for a new layout. Layouts can specify if some + * displays should be disabled (OFF). When switching from one layout to another, we go + * through each of the displays and make sure any displays we might have disabled are + * enabled again. + */ + private void resetLayoutLocked() { + final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState); + for (int i = layout.size() - 1; i >= 0; i--) { + final Layout.Display displayLayout = layout.getAt(i); + final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId()); + if (display != null) { + enableDisplayLocked(display, true); // Reset all displays back to enabled + } } } + /** - * Updates all existing logical displays given the current set of display devices. - * Removes invalid logical displays. Sends notifications if needed. + * Apply (or reapply) the currently selected display layout. */ - private void updateLogicalDisplaysLocked() { - for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) { - final int displayId = mLogicalDisplays.keyAt(i); - LogicalDisplay display = mLogicalDisplays.valueAt(i); + private void applyLayoutLocked() { + final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState); + mCurrentLayout = layout; + Slog.i(TAG, "Applying the display layout for device state(" + mDeviceState + + "): " + layout); - mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); - display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo); - DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides = - display.getFrameRateOverrides(); - display.updateLocked(mDisplayDeviceRepo); - final DisplayGroup changedDisplayGroup; - if (!display.isValidLocked()) { - mLogicalDisplays.removeAt(i); - final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId); - displayGroup.removeDisplayLocked(display); - - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED); - - changedDisplayGroup = displayGroup; - } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) { - final int flags = display.getDisplayInfoLocked().flags; - final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get( - Display.DEFAULT_DISPLAY); - if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) { - // The display should have its own DisplayGroup. - if (defaultDisplayGroup.removeDisplayLocked(display)) { - final int groupId = assignDisplayGroupIdLocked(false); - final DisplayGroup displayGroup = new DisplayGroup(groupId); - displayGroup.addDisplayLocked(display); - display.updateDisplayGroupIdLocked(groupId); - mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup); - mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED); - changedDisplayGroup = defaultDisplayGroup; - } else { - changedDisplayGroup = null; - } - } else { - // The display should be a part of the default DisplayGroup. - final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId); - if (displayGroup != defaultDisplayGroup) { - displayGroup.removeDisplayLocked(display); - defaultDisplayGroup.addDisplayLocked(display); - display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId()); - mListener.onDisplayGroupEventLocked(defaultDisplayGroup.getGroupId(), - LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED); - mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup); - changedDisplayGroup = displayGroup; - } else { - changedDisplayGroup = null; - } - } + // Go through each of the displays in the current layout set. + final int size = layout.size(); + for (int i = 0; i < size; i++) { + final Layout.Display displayLayout = layout.getAt(i); + + // If the underlying display-device we want to use for this display + // doesn't exist, then skip it. This can happen at startup as display-devices + // trickle in one at a time. When the new display finally shows up, the layout is + // recalculated so that the display is properly added to the current layout. + final DisplayAddress address = displayLayout.getAddress(); + final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address); + if (device == null) { + Slog.w(TAG, "The display device (" + address + "), is not available" + + " for the display state " + mDeviceState); + continue; + } - final String oldUniqueId = mTempDisplayInfo.uniqueId; - final String newUniqueId = display.getDisplayInfoLocked().uniqueId; - final int eventMsg = TextUtils.equals(oldUniqueId, newUniqueId) - ? LOGICAL_DISPLAY_EVENT_CHANGED : LOGICAL_DISPLAY_EVENT_SWAPPED; - mListener.onLogicalDisplayEventLocked(display, eventMsg); - } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) { - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); - changedDisplayGroup = null; - } else { - // While applications shouldn't know nor care about the non-overridden info, we - // still need to let WindowManager know so it can update its own internal state for - // things like display cutouts. - display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo); - if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) { - mListener.onLogicalDisplayEventLocked(display, - LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED); - } - changedDisplayGroup = null; + // Now that we have a display-device, we need a LogicalDisplay to map it to. Find the + // right one, if it doesn't exist, create a new one. + final int logicalDisplayId = displayLayout.getLogicalDisplayId(); + LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId); + if (newDisplay == null) { + newDisplay = createNewLogicalDisplayLocked( + null /*displayDevice*/, logicalDisplayId); } - // CHANGED and REMOVED DisplayGroup events should always fire after Display events. - if (changedDisplayGroup != null) { - final int event = changedDisplayGroup.isEmptyLocked() - ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED - : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED; - mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event); + // Now swap the underlying display devices between the old display and the new display + final LogicalDisplay oldDisplay = getDisplayLocked(device); + if (newDisplay != oldDisplay) { + newDisplay.swapDisplaysLocked(oldDisplay); } + enableDisplayLocked(newDisplay, displayLayout.isEnabled()); } } - private int assignDisplayGroupIdLocked(boolean isDefault) { - return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++; + + /** + * Creates a new logical display for the specified device and display Id and adds it to the list + * of logical displays. + * + * @param device The device to associate with the LogicalDisplay. + * @param displayId The display ID to give the new display. If invalid, a new ID is assigned. + * @param isDefault Indicates if we are creating the default display. + * @return The new logical display if created, null otherwise. + */ + private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) { + final int layerStack = assignLayerStackLocked(displayId); + final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); + display.updateLocked(mDisplayDeviceRepo); + mLogicalDisplays.put(displayId, display); + enableDisplayLocked(display, device != null); + return display; + } + + private void enableDisplayLocked(LogicalDisplay display, boolean isEnabled) { + final int displayId = display.getDisplayIdLocked(); + final DisplayInfo info = display.getDisplayInfoLocked(); + + final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode + && (info.type != Display.TYPE_INTERNAL); + if (isEnabled && disallowSecondaryDisplay) { + Slog.i(TAG, "Not creating a logical display for a secondary display because single" + + " display demo mode is enabled: " + display.getDisplayInfoLocked()); + isEnabled = false; + } + + display.setEnabled(isEnabled); + } + + private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) { + return isOwnDisplayGroup ? mNextNonDefaultGroupId++ : Display.DEFAULT_DISPLAY_GROUP; + } + + private void initializeInternalDisplayDeviceLocked(DisplayDevice device) { + // We always want to make sure that our default display layout creates a logical + // display for every internal display device that is found. + // To that end, when we are notified of a new internal display, we add it to + // the default definition if it is not already there. + final Layout layoutSet = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT); + final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + final boolean isDefault = (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; + layoutSet.createDisplayLocked(info.address, isDefault, true /* isEnabled */); } private int assignLayerStackLocked(int displayId) { diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index 18f39e6ade1d..ef336674df5d 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -57,7 +57,7 @@ public class Layout { * @return The new layout. */ public Display createDisplayLocked( - @NonNull DisplayAddress address, boolean isDefault) { + @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); return null; @@ -74,7 +74,7 @@ public class Layout { // different layouts, a logical display can be destroyed and later recreated with the // same logical display ID. final int logicalDisplayId = assignDisplayIdLocked(isDefault); - final Display layout = new Display(address, logicalDisplayId); + final Display layout = new Display(address, logicalDisplayId, isEnabled); mDisplays.add(layout); return layout; @@ -130,17 +130,25 @@ public class Layout { * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. */ public static class Display { + // Address of the display device to map to this display. private final DisplayAddress mAddress; + + // Logical Display ID to apply to this display. private final int mLogicalDisplayId; - Display(@NonNull DisplayAddress address, int logicalDisplayId) { + // Indicates that this display is not usable and should remain off. + private final boolean mIsEnabled; + + Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) { mAddress = address; mLogicalDisplayId = logicalDisplayId; + mIsEnabled = isEnabled; } @Override public String toString() { - return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}"; + return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + + "(" + (mIsEnabled ? "ON" : "OFF") + ")}"; } public DisplayAddress getAddress() { @@ -150,5 +158,9 @@ public class Layout { public int getLogicalDisplayId() { return mLogicalDisplayId; } + + public boolean isEnabled() { + return mIsEnabled; + } } } diff --git a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java deleted file mode 100644 index b082b25aea02..000000000000 --- a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.graphics.fonts; - -import android.annotation.NonNull; -import android.util.Slog; - -import java.io.File; -import java.io.IOException; - -/** - * A class to detect font-related native crash. - * - * <p>If a fs-verity protected file is accessed through mmap and corrupted file block is detected, - * SIGBUG signal is generated and the process will crash. To find corrupted files and remove them, - * we use a marker file to detect crash. - * <ol> - * <li>Create a marker file before reading fs-verity protected font files. - * <li>Delete the marker file after reading font files successfully. - * <li>If the marker file is found in the next process startup, it means that the process - * crashed before. We will delete font files to prevent crash loop. - * </ol> - * - * <p>Example usage: - * <pre> - * FontCrashDetector detector = new FontCrashDetector(new File("/path/to/marker_file")); - * if (detector.hasCrashed()) { - * // Do cleanup - * } - * try (FontCrashDetector.MonitoredBlock b = detector.start()) { - * // Read files - * } - * </pre> - * - * <p>This class DOES NOT detect Java exceptions. If a Java exception is thrown while monitoring - * crash, the marker file will be deleted. Creating and deleting marker files are not lightweight. - * Please use this class sparingly with caution. - */ -/* package */ final class FontCrashDetector { - - private static final String TAG = "FontCrashDetector"; - - @NonNull - private final File mMarkerFile; - - /* package */ FontCrashDetector(@NonNull File markerFile) { - mMarkerFile = markerFile; - } - - /* package */ boolean hasCrashed() { - return mMarkerFile.exists(); - } - - /* package */ void clear() { - if (!mMarkerFile.delete()) { - Slog.e(TAG, "Could not delete marker file: " + mMarkerFile); - } - } - - /** Starts crash monitoring. */ - /* package */ MonitoredBlock start() { - try { - mMarkerFile.createNewFile(); - } catch (IOException e) { - Slog.e(TAG, "Could not create marker file: " + mMarkerFile, e); - } - return new MonitoredBlock(); - } - - /** A helper class to monitor crash with try-with-resources syntax. */ - /* package */ class MonitoredBlock implements AutoCloseable { - /** Ends crash monitoring. */ - @Override - public void close() { - clear(); - } - } -} diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 01e839dae07a..900ec905609f 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -63,7 +63,6 @@ public final class FontManagerService extends IFontManager.Stub { private static final String TAG = "FontManagerService"; private static final String FONT_FILES_DIR = "/data/fonts/files"; - private static final String CRASH_MARKER_FILE = "/data/fonts/config/crash.txt"; @Override public FontConfig getFontConfig() { @@ -200,10 +199,6 @@ public final class FontManagerService extends IFontManager.Stub { private final Object mUpdatableFontDirLock = new Object(); @GuardedBy("mUpdatableFontDirLock") - @NonNull - private final FontCrashDetector mFontCrashDetector; - - @GuardedBy("mUpdatableFontDirLock") @Nullable private final UpdatableFontDir mUpdatableFontDir; @@ -217,7 +212,6 @@ public final class FontManagerService extends IFontManager.Stub { private FontManagerService(Context context) { mContext = context; - mFontCrashDetector = new FontCrashDetector(new File(CRASH_MARKER_FILE)); mUpdatableFontDir = createUpdatableFontDir(); initialize(); } @@ -244,19 +238,8 @@ public final class FontManagerService extends IFontManager.Stub { } return; } - if (mFontCrashDetector.hasCrashed()) { - Slog.i(TAG, "Crash detected. Clearing font updates."); - try { - mUpdatableFontDir.clearUpdates(); - } catch (SystemFontException e) { - Slog.e(TAG, "Failed to clear updates.", e); - } - mFontCrashDetector.clear(); - } - try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { - mUpdatableFontDir.loadFontFileMap(); - updateSerializedFontMap(); - } + mUpdatableFontDir.loadFontFileMap(); + updateSerializedFontMap(); } } @@ -286,10 +269,8 @@ public final class FontManagerService extends IFontManager.Stub { FontManager.RESULT_ERROR_VERSION_MISMATCH, "The base config version is older than current."); } - try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { - mUpdatableFontDir.update(requests); - updateSerializedFontMap(); - } + mUpdatableFontDir.update(requests); + updateSerializedFontMap(); } } @@ -300,10 +281,8 @@ public final class FontManagerService extends IFontManager.Stub { "The font updater is disabled."); } synchronized (mUpdatableFontDirLock) { - try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { - mUpdatableFontDir.clearUpdates(); - updateSerializedFontMap(); - } + mUpdatableFontDir.clearUpdates(); + updateSerializedFontMap(); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 8d6bcadb3e2b..7235a921254d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -666,8 +666,19 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mSelectRequestBuffer.process(); resetSelectRequestBuffer(); - addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); - addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); + List<HotplugDetectionAction> hotplugActions + = getActions(HotplugDetectionAction.class); + if (hotplugActions.isEmpty()) { + addAndStartAction( + new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); + } + + List<PowerStatusMonitorAction> powerStatusActions + = getActions(PowerStatusMonitorAction.class); + if (powerStatusActions.isEmpty()) { + addAndStartAction( + new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); + } HdmiDeviceInfo avr = getAvrDeviceInfo(); if (avr != null) { @@ -1062,7 +1073,21 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Ignore this message. return true; } - setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); + boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled(); + boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message); + // Set System Audio Mode according to TV's settings. + // Handle <System Audio Mode Status> here only when + // SystemAudioAutoInitiationAction timeout + HdmiDeviceInfo avr = getAvrDeviceInfo(); + if (avr == null) { + setSystemAudioMode(false); + } else if (avrSystemAudioMode != tvSystemAudioMode) { + addAndStartAction(new SystemAudioActionFromTv(this, avr.getLogicalAddress(), + tvSystemAudioMode, null)); + } else { + setSystemAudioMode(tvSystemAudioMode); + } + return true; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java index 8c404249cfd5..6f7473d60121 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java @@ -106,9 +106,7 @@ public final class HdmiCecStandbyModeHandler { addHandler(Constants.MESSAGE_SET_STREAM_PATH, mBystander); addHandler(Constants.MESSAGE_STANDBY, mBystander); addHandler(Constants.MESSAGE_SET_MENU_LANGUAGE, mBystander); - addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBystander); addHandler(Constants.MESSAGE_USER_CONTROL_RELEASED, mBystander); - addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBystander); addHandler(Constants.MESSAGE_FEATURE_ABORT, mBystander); addHandler(Constants.MESSAGE_INACTIVE_SOURCE, mBystander); addHandler(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, mBystander); @@ -133,6 +131,8 @@ public final class HdmiCecStandbyModeHandler { addHandler(Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID, mBypasser); addHandler(Constants.MESSAGE_GIVE_OSD_NAME, mBypasser); addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser); + addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser); + addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser); addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index b4d9b01f716f..115cafedca93 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -221,7 +221,7 @@ public class HdmiControlService extends SystemService { private int mHdmiCecVolumeControl; // Make sure HdmiCecConfig is instantiated and the XMLs are read. - private final HdmiCecConfig mHdmiCecConfig; + private HdmiCecConfig mHdmiCecConfig; /** * Interface to report send result. @@ -580,6 +580,11 @@ public class HdmiControlService extends SystemService { mHdmiCecNetwork = hdmiCecNetwork; } + @VisibleForTesting + void setHdmiCecConfig(HdmiCecConfig hdmiCecConfig) { + mHdmiCecConfig = hdmiCecConfig; + } + public HdmiCecNetwork getHdmiCecNetwork() { return mHdmiCecNetwork; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d41f4c76861e..4e8fcf7bc1c5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -159,6 +159,7 @@ import com.android.internal.compat.IPlatformCompat; import com.android.internal.content.PackageMonitor; import com.android.internal.inputmethod.CallbackUtils; import com.android.internal.inputmethod.IBooleanResultCallback; +import com.android.internal.inputmethod.IIInputContentUriTokenResultCallback; import com.android.internal.inputmethod.IInputBindResultResultCallback; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodInfoListResultCallback; @@ -3536,6 +3537,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean didStart = false; InputBindResult res = null; + // We shows the IME when the system allows the IME focused target window to restore the + // IME visibility (e.g. switching to the app task when last time the IME is visible). + if (isTextEditor && mWindowManagerInternal.shouldRestoreImeVisibility(windowToken)) { + if (attribute != null) { + res = startInputUncheckedLocked(cs, inputContext, missingMethods, + attribute, startInputFlags, startInputReason); + showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); + } else { + res = InputBindResult.NULL_EDITOR_INFO; + } + return res; + } + switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) { case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) { @@ -5869,87 +5884,102 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void setImeWindowStatus(int vis, int backDisposition) { - mImms.setImeWindowStatus(mToken, vis, backDisposition); + public void setImeWindowStatus(int vis, int backDisposition, + IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.setImeWindowStatus(mToken, vis, backDisposition)); } @BinderThread @Override - public void reportStartInput(IBinder startInputToken) { - mImms.reportStartInput(mToken, startInputToken); + public void reportStartInput(IBinder startInputToken, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.reportStartInput(mToken, startInputToken)); } @BinderThread @Override - public IInputContentUriToken createInputContentUriToken(Uri contentUri, - String packageName) { - return mImms.createInputContentUriToken(mToken, contentUri, packageName); + public void createInputContentUriToken(Uri contentUri, String packageName, + IIInputContentUriTokenResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.createInputContentUriToken(mToken, contentUri, packageName)); } @BinderThread @Override - public void reportFullscreenMode(boolean fullscreen) { - mImms.reportFullscreenMode(mToken, fullscreen); + public void reportFullscreenMode(boolean fullscreen, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.reportFullscreenMode(mToken, fullscreen)); } @BinderThread @Override - public void setInputMethod(String id) { - mImms.setInputMethod(mToken, id); + public void setInputMethod(String id, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> mImms.setInputMethod(mToken, id)); } @BinderThread @Override - public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype) { - mImms.setInputMethodAndSubtype(mToken, id, subtype); + public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype, + IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.setInputMethodAndSubtype(mToken, id, subtype)); } @BinderThread @Override - public void hideMySoftInput(int flags) { - mImms.hideMySoftInput(mToken, flags); + public void hideMySoftInput(int flags, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> mImms.hideMySoftInput(mToken, flags)); } @BinderThread @Override - public void showMySoftInput(int flags) { - mImms.showMySoftInput(mToken, flags); + public void showMySoftInput(int flags, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> mImms.showMySoftInput(mToken, flags)); } @BinderThread @Override - public void updateStatusIcon(String packageName, @DrawableRes int iconId) { - mImms.updateStatusIcon(mToken, packageName, iconId); + public void updateStatusIcon(String packageName, @DrawableRes int iconId, + IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.updateStatusIcon(mToken, packageName, iconId)); } @BinderThread @Override - public boolean switchToPreviousInputMethod() { - return mImms.switchToPreviousInputMethod(mToken); + public void switchToPreviousInputMethod(IBooleanResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> mImms.switchToPreviousInputMethod(mToken)); } @BinderThread @Override - public boolean switchToNextInputMethod(boolean onlyCurrentIme) { - return mImms.switchToNextInputMethod(mToken, onlyCurrentIme); + public void switchToNextInputMethod(boolean onlyCurrentIme, + IBooleanResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.switchToNextInputMethod(mToken, onlyCurrentIme)); } @BinderThread @Override - public boolean shouldOfferSwitchingToNextInputMethod() { - return mImms.shouldOfferSwitchingToNextInputMethod(mToken); + public void shouldOfferSwitchingToNextInputMethod( + IBooleanResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.shouldOfferSwitchingToNextInputMethod(mToken)); } @BinderThread @Override - public void notifyUserAction() { - mImms.notifyUserAction(mToken); + public void notifyUserAction(IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> mImms.notifyUserAction(mToken)); } @BinderThread @Override - public void applyImeVisibility(IBinder windowToken, boolean setVisible) { - mImms.applyImeVisibility(mToken, windowToken, setVisible); + public void applyImeVisibility(IBinder windowToken, boolean setVisible, + IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, + () -> mImms.applyImeVisibility(mToken, windowToken, setVisible)); } } } diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java index 3ac148ddab1b..c93c4b1f21b7 100644 --- a/services/core/java/com/android/server/location/GeocoderProxy.java +++ b/services/core/java/com/android/server/location/GeocoderProxy.java @@ -24,7 +24,7 @@ import android.location.IGeocodeProvider; import android.os.IBinder; import android.os.RemoteException; -import com.android.server.ServiceWatcher; +import com.android.server.servicewatcher.ServiceWatcher; import java.util.Collections; diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java index 6ea4bd2b1d6d..e1c87000ea89 100644 --- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java +++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java @@ -25,8 +25,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import com.android.server.ServiceWatcher; -import com.android.server.ServiceWatcher.BoundService; +import com.android.server.servicewatcher.ServiceWatcher; +import com.android.server.servicewatcher.ServiceWatcher.BoundService; /** * Proxy class to bind GmsCore to the ActivityRecognitionHardware. diff --git a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java index bdfa6d7aa67e..c70714932792 100644 --- a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java +++ b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java @@ -29,7 +29,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; -import com.android.server.ServiceWatcher; +import com.android.server.servicewatcher.ServiceWatcher; import java.util.Objects; diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java index 05d0aefe7c89..dbd8dd9eff3b 100644 --- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java @@ -21,7 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.SystemClock; -import android.telephony.PhoneStateListener; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import com.android.server.FgThread; @@ -55,8 +55,8 @@ public class SystemEmergencyHelper extends EmergencyHelper { // TODO: this doesn't account for multisim phones - mTelephonyManager.registerPhoneStateListener(FgThread.getExecutor(), - new EmergencyCallPhoneStateListener()); + mTelephonyManager.registerTelephonyCallback(FgThread.getExecutor(), + new EmergencyCallTelephonyCallback()); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -78,8 +78,8 @@ public class SystemEmergencyHelper extends EmergencyHelper { || mTelephonyManager.isInEmergencySmsMode(); } - private class EmergencyCallPhoneStateListener extends PhoneStateListener implements - PhoneStateListener.CallStateChangedListener { + private class EmergencyCallTelephonyCallback extends TelephonyCallback implements + TelephonyCallback.CallStateListener{ @Override public void onCallStateChanged(int state, String incomingNumber) { diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index fef30f9ea811..2aa6f2869afb 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -29,6 +29,8 @@ import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF; import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; +import static android.os.PowerWhitelistManager.REASON_LOCATION_PROVIDER; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; @@ -227,7 +229,10 @@ public class LocationProviderManager extends BroadcastOptions options = BroadcastOptions.makeBasic(); options.setDontSendToRestrictedApps(true); // allows apps to start a fg service in response to a location PI - options.setTemporaryAppWhitelistDuration(TEMPORARY_APP_ALLOWLIST_DURATION_MS); + options.setTemporaryAppAllowlist(TEMPORARY_APP_ALLOWLIST_DURATION_MS, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_LOCATION_PROVIDER, + ""); Intent intent = new Intent().putExtra(KEY_LOCATION_CHANGED, locationResult.getLastLocation()); diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java index 44b62b3659dc..c86e49bc7948 100644 --- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java @@ -36,9 +36,9 @@ import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; -import com.android.server.ServiceWatcher; -import com.android.server.ServiceWatcher.BoundService; import com.android.server.location.provider.AbstractLocationProvider; +import com.android.server.servicewatcher.ServiceWatcher; +import com.android.server.servicewatcher.ServiceWatcher.BoundService; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 7e00fd69a148..364aa2cb41ae 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -193,6 +193,17 @@ class RebootEscrowManager { 0); } + public int getLoadEscrowDataRetryLimit() { + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); + } + + public int getLoadEscrowDataRetryIntervalSeconds() { + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_interval_seconds", + DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS); + } + public void reportMetric(boolean success) { // TODO(b/179105110) design error code; and report the true value for other fields. FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, 0, 1, 1, @@ -251,11 +262,8 @@ class RebootEscrowManager { List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { Objects.requireNonNull(retryHandler); - final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, - "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); - final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, - "load_escrow_data_retry_interval_seconds", - DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS); + final int retryLimit = mInjector.getLoadEscrowDataRetryLimit(); + final int retryIntervalInSeconds = mInjector.getLoadEscrowDataRetryIntervalSeconds(); if (attemptNumber < retryLimit) { Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber); diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java index 9c471b85eb76..ec80521be2e5 100644 --- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java +++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java @@ -136,7 +136,7 @@ public class ResumeOnRebootServiceProvider { } /** Bind to the service */ - public void bindToService(long timeOut) throws TimeoutException { + public void bindToService(long timeOut) throws RemoteException, TimeoutException { if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { CountDownLatch connectionLatch = new CountDownLatch(1); Intent intent = new Intent(); @@ -210,27 +210,25 @@ public class ResumeOnRebootServiceProvider { private void throwTypedException( ParcelableException exception) - throws IOException { - if (exception.getCause() instanceof IOException) { + throws IOException, RemoteException { + if (exception != null && exception.getCause() instanceof IOException) { exception.maybeRethrow(IOException.class); - } else if (exception.getCause() instanceof IllegalStateException) { - exception.maybeRethrow(IllegalStateException.class); } else { - // This should not happen. Wrap the cause in IllegalStateException so that it - // doesn't disrupt the exception handling - throw new IllegalStateException(exception.getCause()); + // Wrap the exception and throw it as a RemoteException. + throw new RemoteException(TAG + " wrap/unwrap failed", exception, + true /* enableSuppression */, true /* writableStackTrace */); } } private void waitForLatch(CountDownLatch latch, String reason, long timeOut) - throws TimeoutException { + throws RemoteException, TimeoutException { try { if (!latch.await(timeOut, TimeUnit.SECONDS)) { throw new TimeoutException("Latch wait for " + reason + " elapsed"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new IllegalStateException("Latch wait for " + reason + " interrupted"); + throw new RemoteException("Latch wait for " + reason + " interrupted"); } } } diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 7ab8b27075b7..a5763aee6336 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -35,6 +35,7 @@ import android.security.Scrypt; import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -906,7 +907,7 @@ public class SyntheticPasswordManager { if (!tokenMap.containsKey(userId)) { return Collections.emptySet(); } - return tokenMap.get(userId).keySet(); + return new ArraySet<>(tokenMap.get(userId).keySet()); } public boolean removePendingToken(long handle, int userId) { diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 87e170ad12df..cb0a668871e0 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; +import android.os.PowerWhitelistManager; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -195,8 +196,9 @@ final class MediaButtonReceiverHolder { mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName); final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setTemporaryAppWhitelistDuration( - FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS); + options.setTemporaryAppAllowlist(FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS, + PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + PowerWhitelistManager.REASON_MEDIA_BUTTON, ""); if (mPendingIntent != null) { if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent " diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java index 639dda6f8981..23195bbe5d7e 100644 --- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java +++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java @@ -93,8 +93,7 @@ public final class MediaMetricsManagerService extends SystemService { StatsLog.write(statsEvent); } - @Override - public String getSessionId(int userId) { + private String getSessionIdInternal(int userId) { byte[] byteId = new byte[16]; // 128 bits mSecureRandom.nextBytes(byteId); String id = Base64.encodeToString(byteId, Base64.DEFAULT); @@ -102,6 +101,16 @@ public final class MediaMetricsManagerService extends SystemService { } @Override + public String getPlaybackSessionId(int userId) { + return getSessionIdInternal(userId); + } + + @Override + public String getRecordingSessionId(int userId) { + return getSessionIdInternal(userId); + } + + @Override public void reportPlaybackErrorEvent( String sessionId, PlaybackErrorEvent event, int userId) { StatsEvent statsEvent = StatsEvent.newBuilder() diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 3cc32bef0e67..851ea3d01085 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -35,7 +35,6 @@ import android.net.Network; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.os.Handler; -import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; @@ -63,7 +62,6 @@ public class LockdownVpnTracker { @NonNull private final Handler mHandler; @NonNull private final Vpn mVpn; @NonNull private final VpnProfile mProfile; - @NonNull private final KeyStore mKeyStore; @NonNull private final Object mStateLock = new Object(); @@ -132,7 +130,6 @@ public class LockdownVpnTracker { public LockdownVpnTracker(@NonNull Context context, @NonNull Handler handler, - @NonNull KeyStore keyStore, @NonNull Vpn vpn, @NonNull VpnProfile profile) { mContext = Objects.requireNonNull(context); @@ -140,7 +137,6 @@ public class LockdownVpnTracker { mHandler = Objects.requireNonNull(handler); mVpn = Objects.requireNonNull(vpn); mProfile = Objects.requireNonNull(profile); - mKeyStore = Objects.requireNonNull(keyStore); mNotificationManager = mContext.getSystemService(NotificationManager.class); final Intent configIntent = new Intent(ACTION_VPN_SETTINGS); @@ -212,7 +208,7 @@ public class LockdownVpnTracker { // network is the system default. So, if the VPN is up and underlying network // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have // changed to match the new default network (e.g., cell). - mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp); + mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp); } catch (IllegalStateException e) { mAcceptedEgressIface = null; Log.e(TAG, "Failed to start VPN", e); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 067c5c0e7620..a2f2f98b57c7 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -169,7 +169,6 @@ import android.net.NetworkIdentity; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkPolicyManager.UidState; -import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; @@ -3164,14 +3163,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - @Override - @Deprecated - public NetworkQuotaInfo getNetworkQuotaInfo(NetworkState state) { - Log.w(TAG, "Shame on UID " + Binder.getCallingUid() - + " for calling the hidden API getNetworkQuotaInfo(). Shame!"); - return new NetworkQuotaInfo(); - } - private void enforceSubscriptionPlanAccess(int subId, int callingUid, String callingPackage) { // Verify they're not lying about package name mAppOps.checkPackage(callingUid, callingPackage); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 5b9a11bc5a31..e0f534602dde 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -24,7 +24,6 @@ import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.isNetworkTypeMobile; import static android.net.NetworkIdentity.SUBTYPE_COMBINED; import static android.net.NetworkStack.checkNetworkStackPermission; @@ -45,6 +44,7 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileWildcard; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; +import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UNSUPPORTED; @@ -97,7 +97,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkIdentity; import android.net.NetworkStack; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkStats; import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; @@ -296,7 +296,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Last states of all networks sent from ConnectivityService. */ @GuardedBy("mStatsLock") @Nullable - private NetworkState[] mLastNetworkStates = null; + private NetworkStateSnapshot[] mLastNetworkStateSnapshots = null; private final DropBoxNonMonotonicObserver mNonMonotonicObserver = new DropBoxNonMonotonicObserver(); @@ -378,8 +378,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } case MSG_UPDATE_IFACES: { // If no cached states, ignore. - if (mLastNetworkStates == null) break; - updateIfaces(mDefaultNetworks, mLastNetworkStates, mActiveIface); + if (mLastNetworkStateSnapshots == null) break; + // TODO (b/181642673): Protect mDefaultNetworks from concurrent accessing. + updateIfaces(mDefaultNetworks, mLastNetworkStateSnapshots, mActiveIface); break; } case MSG_PERFORM_POLL_REGISTER_ALERT: { @@ -967,10 +968,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - @Override public void forceUpdateIfaces( Network[] defaultNetworks, - NetworkState[] networkStates, + NetworkStateSnapshot[] networkStates, String activeIface, UnderlyingNetworkInfo[] underlyingNetworkInfos) { checkNetworkStackPermission(mContext); @@ -1248,13 +1248,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void updateIfaces( Network[] defaultNetworks, - NetworkState[] networkStates, + NetworkStateSnapshot[] snapshots, String activeIface) { synchronized (mStatsLock) { mWakeLock.acquire(); try { mActiveIface = activeIface; - updateIfacesLocked(defaultNetworks, networkStates); + updateIfacesLocked(defaultNetworks, snapshots); } finally { mWakeLock.release(); } @@ -1262,13 +1262,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Inspect all current {@link NetworkState} to derive mapping from {@code iface} to {@link - * NetworkStatsHistory}. When multiple networks are active on a single {@code iface}, + * Inspect all current {@link NetworkStateSnapshot}s to derive mapping from {@code iface} to + * {@link NetworkStatsHistory}. When multiple networks are active on a single {@code iface}, * they are combined under a single {@link NetworkIdentitySet}. */ @GuardedBy("mStatsLock") - private void updateIfacesLocked(@Nullable Network[] defaultNetworks, - @NonNull NetworkState[] states) { + private void updateIfacesLocked(@NonNull Network[] defaultNetworks, + @NonNull NetworkStateSnapshot[] snapshots) { if (!mSystemReady) return; if (LOGV) Slog.v(TAG, "updateIfacesLocked()"); @@ -1283,26 +1283,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // Rebuild active interfaces based on connected networks mActiveIfaces.clear(); mActiveUidIfaces.clear(); - if (defaultNetworks != null) { - // Caller is ConnectivityService. Update the list of default networks. - mDefaultNetworks = defaultNetworks; - } + // Update the list of default networks. + mDefaultNetworks = defaultNetworks; - mLastNetworkStates = states; + mLastNetworkStateSnapshots = snapshots; final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled(); final ArraySet<String> mobileIfaces = new ArraySet<>(); - for (NetworkState state : states) { - final boolean isMobile = isNetworkTypeMobile(state.legacyNetworkType); - final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, state.network); + for (NetworkStateSnapshot snapshot : snapshots) { + final boolean isMobile = isNetworkTypeMobile(snapshot.legacyType); + final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, snapshot.network); final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED - : getSubTypeForState(state); - final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state, + : getSubTypeForStateSnapshot(snapshot); + final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, isDefault, subType); // Traffic occurring on the base interface is always counted for // both total usage and UID details. - final String baseIface = state.linkProperties.getInterfaceName(); + final String baseIface = snapshot.linkProperties.getInterfaceName(); if (baseIface != null) { findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); @@ -1312,7 +1310,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // If IMS is metered, then the IMS network usage has already included VT usage. // VT is considered always metered in framework's layer. If VT is not metered // per carrier's policy, modem will report 0 usage for VT calls. - if (state.networkCapabilities.hasCapability( + if (snapshot.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) { // Copy the identify from IMS one but mark it as metered. @@ -1358,7 +1356,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // (or non eBPF offloaded) TX they would appear on both, however egress interface // accounting is explicitly bypassed for traffic from the clat uid. // - final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); + final List<LinkProperties> stackedLinks = snapshot.linkProperties.getStackedLinks(); for (LinkProperties stackedLink : stackedLinks) { final String stackedIface = stackedLink.getInterfaceName(); if (stackedIface != null) { @@ -1381,7 +1379,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different * transport types do not actually fill this value. */ - private int getSubTypeForState(@NonNull NetworkState state) { + private int getSubTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) { if (!state.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { return 0; } diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 9a9b14c31314..b34611b9cd6f 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -207,35 +207,37 @@ public class DataLoaderManagerService extends SystemService { @Override public void onServiceDisconnected(ComponentName arg0) { Slog.i(TAG, "DataLoader " + mId + " disconnected, but will try to recover"); - callListener(IDataLoaderStatusListener.DATA_LOADER_DESTROYED); - destroy(); + unbindAndReportDestroyed(); } @Override public void onBindingDied(ComponentName name) { Slog.i(TAG, "DataLoader " + mId + " died"); - callListener(IDataLoaderStatusListener.DATA_LOADER_DESTROYED); - destroy(); + unbindAndReportDestroyed(); } @Override public void onNullBinding(ComponentName name) { Slog.i(TAG, "DataLoader " + mId + " failed to start"); - callListener(IDataLoaderStatusListener.DATA_LOADER_DESTROYED); - destroy(); + unbindAndReportDestroyed(); } @Override public void binderDied() { Slog.i(TAG, "DataLoader " + mId + " died"); - callListener(IDataLoaderStatusListener.DATA_LOADER_DESTROYED); - destroy(); + unbindAndReportDestroyed(); } IDataLoader getDataLoader() { return mDataLoader; } + private void unbindAndReportDestroyed() { + if (unbind()) { + callListener(IDataLoaderStatusListener.DATA_LOADER_DESTROYED); + } + } + void destroy() { if (mDataLoader != null) { try { @@ -244,11 +246,15 @@ public class DataLoaderManagerService extends SystemService { } mDataLoader = null; } + unbind(); + } + + boolean unbind() { try { mContext.unbindService(this); } catch (Exception ignored) { } - remove(); + return remove(); } private boolean append() { @@ -266,12 +272,14 @@ public class DataLoaderManagerService extends SystemService { } } - private void remove() { + private boolean remove() { synchronized (mServiceConnections) { if (mServiceConnections.get(mId) == this) { mServiceConnections.remove(mId); + return true; } } + return false; } private void callListener(int status) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 460b2f2bf5c6..903652ab76a5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2086,15 +2086,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { sealLocked(); - // Session that are staged, ready and not multi package will be installed during - // this boot. As such, we need populate all the fields for successful installation. - if (isMultiPackage()) { + // Session that are staged, committed and not multi package will be installed or + // restart verification during this boot. As such, we need populate all the fields + // for successful installation. + if (isMultiPackage() || !isStaged() || !isCommitted()) { return; } final PackageInstallerSession root = hasParentSessionId() ? allSessions.get(getParentSessionId()) : this; - if (root != null && root.isStagedSessionReady()) { + if (root != null) { if (isApexSession()) { validateApexInstallLocked(); } else { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index af2fdf70ca50..b751503907fd 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2636,7 +2636,8 @@ public class PackageManagerService extends IPackageManager.Stub // We'll want to include browser possibilities in a few cases boolean includeBrowser = false; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, + matchFlags)) { result.addAll(undefinedList); // Maybe add one for the other profile. if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel @@ -2820,8 +2821,8 @@ public class PackageManagerService extends IPackageManager.Stub } result.highestApprovalLevel = Math.max(mDomainVerificationManager - .approvalLevelForDomain(ps, intent, flags, riTargetUser.targetUserId), - result.highestApprovalLevel); + .approvalLevelForDomain(ps, intent, resultTargetUser, flags, + riTargetUser.targetUserId), result.highestApprovalLevel); } if (result != null && result.highestApprovalLevel <= DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) { @@ -3067,8 +3068,8 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = info.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps.getInstantApp(userId)) { - if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, - userId)) { + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + instantApps, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "Instant app approved for intent; pkg: " + packageName); @@ -3995,8 +3996,8 @@ public class PackageManagerService extends IPackageManager.Stub if (ps != null) { // only check domain verification status if the app is not a browser if (!info.handleAllWebDataURI) { - if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, - userId)) { + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + resolvedActivities, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName + ", approved"); @@ -9575,7 +9576,7 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = ri.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps, - intent, flags, userId)) { + intent, query, flags, userId)) { return ri; } } @@ -9632,10 +9633,10 @@ public class PackageManagerService extends IPackageManager.Stub */ private static boolean hasAnyDomainApproval( @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting, - @NonNull Intent intent, @PackageManager.ResolveInfoFlags int resolveInfoFlags, - @UserIdInt int userId) { - return manager.approvalLevelForDomain(pkgSetting, intent, resolveInfoFlags, userId) - > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; + @NonNull Intent intent, @NonNull List<ResolveInfo> candidates, + @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { + return manager.approvalLevelForDomain(pkgSetting, intent, candidates, resolveInfoFlags, + userId) > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; } /** @@ -11633,9 +11634,17 @@ public class PackageManagerService extends IPackageManager.Stub healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS; healthCheckParams.unhealthyMonitoringMs = INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS; + // Continue monitoring health and loading progress of active incremental packages mIncrementalManager.registerHealthListener(parsedPackage.getPath(), healthCheckParams, new IncrementalHealthListener(parsedPackage.getPackageName())); + final IncrementalStatesCallback incrementalStatesCallback = + new IncrementalStatesCallback(parsedPackage.getPackageName(), + UserHandle.getUid(UserHandle.ALL, pkgSetting.appId), + getInstalledUsers(pkgSetting, UserHandle.USER_ALL)); + pkgSetting.setIncrementalStatesCallback(incrementalStatesCallback); + mIncrementalManager.registerLoadingProgressCallback(parsedPackage.getPath(), + new IncrementalProgressListener(parsedPackage.getPackageName())); } } return scanResult.pkgSetting.pkg; @@ -17984,7 +17993,9 @@ public class PackageManagerService extends IPackageManager.Stub try { makeDirRecursive(afterCodeFile.getParentFile(), 0775); if (onIncremental) { - mIncrementalManager.renameCodePath(beforeCodeFile, afterCodeFile); + // Just link files here. The stage dir will be removed when the installation + // session is completed. + mIncrementalManager.linkCodePath(beforeCodeFile, afterCodeFile); } else { Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath()); } @@ -17993,7 +18004,6 @@ public class PackageManagerService extends IPackageManager.Stub return false; } - //TODO(b/136132412): enable selinux restorecon for incremental directories if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) { Slog.w(TAG, "Failed to restorecon"); return false; @@ -19419,6 +19429,8 @@ public class PackageManagerService extends IPackageManager.Stub mIncrementalManager.unregisterLoadingProgressCallbacks(codePath); // Unregister health listener as it will always be healthy from now mIncrementalManager.unregisterHealthListener(codePath); + // Make sure the information is preserved + scheduleWriteSettingsLocked(); } @Override @@ -19481,11 +19493,11 @@ public class PackageManagerService extends IPackageManager.Stub final PackageSetting ps; synchronized (mLock) { ps = mSettings.getPackageLPr(mPackageName); + if (ps == null) { + return; + } + ps.setLoadingProgress(progress); } - if (ps == null) { - return; - } - ps.setLoadingProgress(progress); } } @@ -20089,7 +20101,7 @@ public class PackageManagerService extends IPackageManager.Stub } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR, - "Error deriving application ABI"); + "Error deriving application ABI: " + pme.getMessage()); } } @@ -20356,6 +20368,11 @@ public class PackageManagerService extends IPackageManager.Stub return; } + if (isIncrementalPath(pkg.getPath()) && IncrementalManager.getVersion() + < IncrementalManager.MIN_VERSION_TO_SUPPORT_FSVERITY) { + return; + } + // Collect files we care for fs-verity setup. ArrayMap<String, String> fsverityCandidates = new ArrayMap<>(); if (legacyMode) { @@ -26117,6 +26134,11 @@ public class PackageManagerService extends IPackageManager.Stub return PackageManagerService.this.hasSigningCertificate( packageName, certificate, CERT_INPUT_SHA256); } + + @Override + public boolean hasSystemFeature(String featureName, int version) { + return PackageManagerService.this.hasSystemFeature(featureName, version); + } } private AndroidPackage getPackage(String packageName) { diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java index ec643f598041..c53fef72db9c 100644 --- a/services/core/java/com/android/server/pm/SettingsXml.java +++ b/services/core/java/com/android/server/pm/SettingsXml.java @@ -83,7 +83,7 @@ public class SettingsXml { @Override public void close() throws IOException { mWriteSection.closeCompletely(); - mXmlSerializer.endDocument(); + mXmlSerializer.flush(); } } diff --git a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java index 1c5f0a7fc0f3..f411c98433cf 100644 --- a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java +++ b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java @@ -280,7 +280,8 @@ public class ShortcutBitmapSaver { IoUtils.closeQuietly(out); } - shortcut.setBitmapPath(file.getAbsolutePath()); + final String path = file.getAbsolutePath(); + mService.postValue(shortcut, si -> si.setBitmapPath(path)); } catch (IOException | RuntimeException e) { Slog.e(ShortcutService.TAG, "Unable to write bitmap to file", e); @@ -295,12 +296,14 @@ public class ShortcutBitmapSaver { Slog.d(TAG, "Saved bitmap."); } if (shortcut != null) { - if (shortcut.getBitmapPath() == null) { - removeIcon(shortcut); - } + mService.postValue(shortcut, si -> { + if (si.getBitmapPath() == null) { + removeIcon(si); + } - // Whatever happened, remove this flag. - shortcut.clearFlags(ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE); + // Whatever happened, remove this flag. + si.clearFlags(ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE); + }); } } return true; diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index a604afc22c09..302e6572a245 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -223,12 +223,14 @@ class ShortcutPackage extends ShortcutPackageItem { // - Disable if needed. for (int i = mShortcuts.size() - 1; i >= 0; i--) { ShortcutInfo si = mShortcuts.valueAt(i); - si.clearFlags(ShortcutInfo.FLAG_SHADOW); + mutateShortcut(si.getId(), si, shortcut -> { + shortcut.clearFlags(ShortcutInfo.FLAG_SHADOW); - si.setDisabledReason(restoreBlockReason); - if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { - si.addFlags(ShortcutInfo.FLAG_DISABLED); - } + shortcut.setDisabledReason(restoreBlockReason); + if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { + shortcut.addFlags(ShortcutInfo.FLAG_DISABLED); + } + }); } // Because some launchers may not have been restored (e.g. allowBackup=false), // we need to re-calculate the pinned shortcuts. @@ -460,9 +462,11 @@ class ShortcutPackage extends ShortcutPackageItem { if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) { changed = true; - si.setTimestamp(now); - si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); - si.setRank(0); // It may still be pinned, so clear the rank. + mutateShortcut(si.getId(), si, shortcut -> { + shortcut.setTimestamp(now); + shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC); + shortcut.setRank(0); // It may still be pinned, so clear the rank. + }); } } if (changed) { @@ -506,7 +510,7 @@ class ShortcutPackage extends ShortcutPackageItem { public ShortcutInfo deleteLongLivedWithId(@NonNull String shortcutId, boolean ignoreInvisible) { final ShortcutInfo shortcut = mShortcuts.get(shortcutId); if (shortcut != null) { - shortcut.clearFlags(ShortcutInfo.FLAG_CACHED_ALL); + mutateShortcut(shortcutId, null, si -> si.clearFlags(ShortcutInfo.FLAG_CACHED_ALL)); } return deleteOrDisableWithId( shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible, @@ -527,15 +531,16 @@ class ShortcutPackage extends ShortcutPackageItem { overrideImmutable, ignoreInvisible, disabledReason); // If disabled id still exists, it is pinned and we need to update the disabled message. - final ShortcutInfo disabled = mShortcuts.get(shortcutId); - if (disabled != null) { - if (disabledMessage != null) { - disabled.setDisabledMessage(disabledMessage); - } else if (disabledMessageResId != 0) { - disabled.setDisabledMessageResId(disabledMessageResId); - mShortcutUser.mService.fixUpShortcutResourceNamesAndValues(disabled); + mutateShortcut(shortcutId, null, disabled -> { + if (disabled != null) { + if (disabledMessage != null) { + disabled.setDisabledMessage(disabledMessage); + } else if (disabledMessageResId != 0) { + disabled.setDisabledMessageResId(disabledMessageResId); + mShortcutUser.mService.fixUpShortcutResourceNamesAndValues(disabled); + } } - } + }); return deleted; } @@ -557,21 +562,23 @@ class ShortcutPackage extends ShortcutPackageItem { } if (oldShortcut.isPinned() || oldShortcut.isCached()) { - oldShortcut.setRank(0); - oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); - if (disable) { - oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED); - // Do not overwrite the disabled reason if one is alreay set. - if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { - oldShortcut.setDisabledReason(disabledReason); + mutateShortcut(oldShortcut.getId(), oldShortcut, si -> { + si.setRank(0); + si.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); + if (disable) { + si.addFlags(ShortcutInfo.FLAG_DISABLED); + // Do not overwrite the disabled reason if one is alreay set. + if (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { + si.setDisabledReason(disabledReason); + } } - } - oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis()); + si.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis()); - // See ShortcutRequestPinProcessor.directPinShortcut(). - if (mShortcutUser.mService.isDummyMainActivity(oldShortcut.getActivity())) { - oldShortcut.setActivity(null); - } + // See ShortcutRequestPinProcessor.directPinShortcut(). + if (mShortcutUser.mService.isDummyMainActivity(si.getActivity())) { + si.setActivity(null); + } + }); return null; } else { @@ -581,12 +588,11 @@ class ShortcutPackage extends ShortcutPackageItem { } public void enableWithId(@NonNull String shortcutId) { - final ShortcutInfo shortcut = mShortcuts.get(shortcutId); - if (shortcut != null) { - ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true); - shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED); - shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); - } + mutateShortcut(shortcutId, null, si -> { + ensureNotImmutable(si, /*ignoreInvisible=*/ true); + si.clearFlags(ShortcutInfo.FLAG_DISABLED); + si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + }); } public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) { @@ -609,22 +615,25 @@ class ShortcutPackage extends ShortcutPackageItem { * <p>Then remove all shortcuts that are not dynamic and no longer pinned either. */ public void refreshPinnedFlags() { - // First, un-pin all shortcuts - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED); + // TODO: rewrite this function with proper query (i.e. fetch only pinned shortcuts and + // unpin if it's no longer pinned by any launcher and vice versa) + final List<ShortcutInfo> shortcuts = new ArrayList<>(mShortcuts.values()); + final Map<String, ShortcutInfo> shortcutMap = new ArrayMap<>(shortcuts.size()); + for (ShortcutInfo si : shortcuts) { + shortcutMap.put(si.getId(), si); } + final Set<String> pinnedShortcuts = new ArraySet<>(); - // Then, for the pinned set for each launcher, set the pin flag one by one. + // First, for the pinned set for each launcher, keep track of their id one by one. mShortcutUser.forAllLaunchers(launcherShortcuts -> { final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds( getPackageName(), getPackageUserId()); - if (pinned == null || pinned.size() == 0) { return; } for (int i = pinned.size() - 1; i >= 0; i--) { final String id = pinned.valueAt(i); - final ShortcutInfo si = mShortcuts.get(id); + final ShortcutInfo si = shortcutMap.get(id); if (si == null) { // This happens if a launcher pinned shortcuts from this package, then backup& // restored, but this package doesn't allow backing up. @@ -632,9 +641,21 @@ class ShortcutPackage extends ShortcutPackageItem { // That's fine, when the launcher is restored, we'll fix it. continue; } - si.addFlags(ShortcutInfo.FLAG_PINNED); + pinnedShortcuts.add(si.getId()); } }); + // Then, update the pinned state if necessary + for (int i = shortcuts.size() - 1; i >= 0; i--) { + final ShortcutInfo si = shortcuts.get(i); + if (pinnedShortcuts.contains(si.getId()) && !si.isPinned()) { + mutateShortcut(si.getId(), si, + shortcut -> shortcut.addFlags(ShortcutInfo.FLAG_PINNED)); + } + if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) { + mutateShortcut(si.getId(), si, shortcut -> + shortcut.clearFlags(ShortcutInfo.FLAG_PINNED)); + } + } // Lastly, remove the ones that are no longer pinned, cached nor dynamic. removeOrphans(); @@ -1034,8 +1055,10 @@ class ShortcutPackage extends ShortcutPackageItem { continue; } Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId())); - si.clearFlags(ShortcutInfo.FLAG_DISABLED); - si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + mutateShortcut(si.getId(), si, shortcut -> { + shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED); + shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + }); } // For existing shortcuts, update timestamps if they have any resources. @@ -1065,21 +1088,24 @@ class ShortcutPackage extends ShortcutPackageItem { } if (si.hasAnyResources()) { - if (!si.isOriginallyFromManifest()) { + if (publisherRes == null) { + publisherRes = getPackageResources(); if (publisherRes == null) { - publisherRes = getPackageResources(); - if (publisherRes == null) { - break; // Resources couldn't be loaded. - } + break; // Resources couldn't be loaded. + } + } + + final Resources res = publisherRes; + mutateShortcut(si.getId(), si, shortcut -> { + if (!shortcut.isOriginallyFromManifest()) { + shortcut.lookupAndFillInResourceIds(res); } - // TODO: update resource strings in AppSearch // If this shortcut is not from a manifest, then update all resource IDs // from resource names. (We don't allow resource strings for // non-manifest at the moment, but icons can still be resources.) - si.lookupAndFillInResourceIds(publisherRes); - } - si.setTimestamp(s.injectCurrentTimeMillis()); + shortcut.setTimestamp(s.injectCurrentTimeMillis()); + }); } } } @@ -1382,8 +1408,11 @@ class ShortcutPackage extends ShortcutPackageItem { } } - si.resolveResourceStrings(publisherRes); - si.setTimestamp(s.injectCurrentTimeMillis()); + final Resources res = publisherRes; + mutateShortcut(si.getId(), si, shortcut -> { + shortcut.resolveResourceStrings(res); + shortcut.setTimestamp(s.injectCurrentTimeMillis()); + }); if (changedShortcuts == null) { changedShortcuts = new ArrayList<>(1); @@ -1400,7 +1429,7 @@ class ShortcutPackage extends ShortcutPackageItem { public void clearAllImplicitRanks() { for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); - si.clearImplicitRankAndRankChangedFlag(); + mutateShortcut(si.getId(), si, ShortcutInfo::clearImplicitRankAndRankChangedFlag); } } @@ -1445,8 +1474,10 @@ class ShortcutPackage extends ShortcutPackageItem { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.isFloating()) { if (si.getRank() != 0) { - si.setTimestamp(now); - si.setRank(0); + mutateShortcut(si.getId(), si, shortcut -> { + shortcut.setTimestamp(now); + shortcut.setRank(0); + }); } } } @@ -1479,8 +1510,10 @@ class ShortcutPackage extends ShortcutPackageItem { } final int thisRank = rank++; if (si.getRank() != thisRank) { - si.setTimestamp(now); - si.setRank(thisRank); + mutateShortcut(si.getId(), si, shortcut -> { + shortcut.setTimestamp(now); + shortcut.setRank(thisRank); + }); } } } @@ -2172,6 +2205,32 @@ class ShortcutPackage extends ShortcutPackageItem { resetAppSearch(null); } + void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut, + @NonNull final Consumer<ShortcutInfo> transform) { + Objects.requireNonNull(id); + Objects.requireNonNull(transform); + synchronized (mLock) { + if (shortcut != null) { + transform.accept(shortcut); + } else { + transform.accept(findShortcutById(id)); + } + // TODO: Load ShortcutInfo from AppSearch, apply transformation logic and save + } + } + + /** + * Removes shortcuts from AppSearch. + */ + void removeShortcuts() { + } + + /** + * Merge/replace shortcuts parsed from xml file. + */ + void restoreParsedShortcuts(final boolean replace) { + } + private boolean verifyRanksSequential(List<ShortcutInfo> list) { boolean failed = false; diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 209a143f665d..d1cf55de7254 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -103,6 +103,7 @@ import android.view.IWindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.BackgroundThread; import com.android.internal.util.CollectionUtils; @@ -142,6 +143,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; @@ -362,6 +364,7 @@ public class ShortcutService extends IShortcutService.Stub { private List<Integer> mDirtyUserIds = new ArrayList<>(); private final AtomicBoolean mBootCompleted = new AtomicBoolean(); + private final AtomicBoolean mShutdown = new AtomicBoolean(); /** * Note we use a fine-grained lock for {@link #mUnlockedUsers} due to b/64303666. @@ -498,6 +501,12 @@ public class ShortcutService extends IShortcutService.Stub { mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, localeFilter, null, mHandler); + IntentFilter shutdownFilter = new IntentFilter(); + shutdownFilter.addAction(Intent.ACTION_SHUTDOWN); + shutdownFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + mContext.registerReceiverAsUser(mShutdownReceiver, UserHandle.SYSTEM, + shutdownFilter, null, mHandler); + injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE); @@ -662,7 +671,7 @@ public class ShortcutService extends IShortcutService.Stub { /** lifecycle event */ void handleUnlockUser(int userId) { if (DEBUG) { - Slog.d(TAG, "handleUnlockUser: user=" + userId); + Slog.d(TAG, "handleUnlockUser: user=" + userId); } synchronized (mUnlockedUsers) { mUnlockedUsers.put(userId, true); @@ -1162,6 +1171,9 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "saveDirtyInfo"); } + if (mShutdown.get()) { + return; + } try { synchronized (mLock) { for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) { @@ -1179,6 +1191,14 @@ public class ShortcutService extends IShortcutService.Stub { } } + void postValue(@NonNull final ShortcutInfo shortcutInfo, + @NonNull final Consumer<ShortcutInfo> cb) { + final String pkg = shortcutInfo.getPackage(); + final int userId = shortcutInfo.getUserId(); + final String id = shortcutInfo.getId(); + getPackageShortcutsLocked(pkg, userId).mutateShortcut(id, shortcutInfo, cb); + } + /** Return the last reset time. */ @GuardedBy("mLock") long getLastResetTimeLocked() { @@ -1566,7 +1586,6 @@ public class ShortcutService extends IShortcutService.Stub { * resource-based strings. */ void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) { - // TODO: update resource names in AppSearch final Resources publisherRes = injectGetResourcesForApplicationAsUser( si.getPackage(), si.getUserId()); if (publisherRes != null) { @@ -1868,8 +1887,8 @@ public class ShortcutService extends IShortcutService.Stub { // === APIs === @Override - public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, - @UserIdInt int userId) { + public void setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId, @NonNull AndroidFuture callback) { verifyCaller(packageName, userId); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); @@ -1896,7 +1915,7 @@ public class ShortcutService extends IShortcutService.Stub { // Throttling. if (!ps.tryApiCall(unlimited)) { - return false; + callback.complete(false); } // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). @@ -1932,12 +1951,12 @@ public class ShortcutService extends IShortcutService.Stub { verifyStates(); - return true; + callback.complete(true); } @Override - public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, - @UserIdInt int userId) { + public void updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId, AndroidFuture callback) { verifyCaller(packageName, userId); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); @@ -1947,7 +1966,7 @@ public class ShortcutService extends IShortcutService.Stub { final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( injectBinderCallingPid(), injectBinderCallingUid()); - List<ShortcutInfo> changedShortcuts = null; + final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); synchronized (mLock) { throwIfUserLockedL(userId); @@ -1964,7 +1983,8 @@ public class ShortcutService extends IShortcutService.Stub { // Throttling. if (!ps.tryApiCall(unlimited)) { - return false; + callback.complete(false); + return; } // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). @@ -1975,68 +1995,66 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutInfo source = newShortcuts.get(i); fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); - final ShortcutInfo target = ps.findShortcutById(source.getId()); - - // Invisible shortcuts can't be updated. - if (target == null || !target.isVisibleToPublisher()) { - continue; - } + ps.mutateShortcut(source.getId(), null, target -> { + // Invisible shortcuts can't be updated. + if (target == null || !target.isVisibleToPublisher()) { + return; + } - if (target.isEnabled() != source.isEnabled()) { - Slog.w(TAG, - "ShortcutInfo.enabled cannot be changed with updateShortcuts()"); - } + if (target.isEnabled() != source.isEnabled()) { + Slog.w(TAG, + "ShortcutInfo.enabled cannot be changed with updateShortcuts()"); + } - if (target.isLongLived() != source.isLongLived()) { - Slog.w(TAG, - "ShortcutInfo.longLived cannot be changed with updateShortcuts()"); - } + if (target.isLongLived() != source.isLongLived()) { + Slog.w(TAG, + "ShortcutInfo.longLived cannot be changed with updateShortcuts()"); + } - // When updating the rank, we need to insert between existing ranks, so set - // this setRankChanged, and also copy the implicit rank fo adjustRanks(). - if (source.hasRank()) { - target.setRankChanged(); - target.setImplicitRank(source.getImplicitRank()); - } + // When updating the rank, we need to insert between existing ranks, so set + // this setRankChanged, and also copy the implicit rank fo adjustRanks(). + if (source.hasRank()) { + target.setRankChanged(); + target.setImplicitRank(source.getImplicitRank()); + } - final boolean replacingIcon = (source.getIcon() != null); - if (replacingIcon) { - removeIconLocked(target); - } + final boolean replacingIcon = (source.getIcon() != null); + if (replacingIcon) { + removeIconLocked(target); + } - // Note copyNonNullFieldsFrom() does the "updatable with?" check too. - target.copyNonNullFieldsFrom(source); - target.setTimestamp(injectCurrentTimeMillis()); + // Note copyNonNullFieldsFrom() does the "updatable with?" check too. + target.copyNonNullFieldsFrom(source); + target.setTimestamp(injectCurrentTimeMillis()); - if (replacingIcon) { - saveIconAndFixUpShortcutLocked(target); - } + if (replacingIcon) { + saveIconAndFixUpShortcutLocked(target); + } - // When we're updating any resource related fields, re-extract the res names and - // the values. - if (replacingIcon || source.hasStringResources()) { - fixUpShortcutResourceNamesAndValues(target); - } + // When we're updating any resource related fields, re-extract the res names and + // the values. + if (replacingIcon || source.hasStringResources()) { + fixUpShortcutResourceNamesAndValues(target); + } - if (changedShortcuts == null) { - changedShortcuts = new ArrayList<>(1); - } - changedShortcuts.add(target); + changedShortcuts.add(target); + }); } // Lastly, adjust the ranks. ps.adjustRanks(); } - packageShortcutsChanged(packageName, userId, changedShortcuts, null); + packageShortcutsChanged(packageName, userId, + changedShortcuts.isEmpty() ? null : changedShortcuts, null); verifyStates(); - return true; + callback.complete(true); } @Override - public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, - @UserIdInt int userId) { + public void addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId, AndroidFuture callback) { verifyCaller(packageName, userId); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); @@ -2066,7 +2084,8 @@ public class ShortcutService extends IShortcutService.Stub { // Throttling. if (!ps.tryApiCall(unlimited)) { - return false; + callback.complete(false); + return; } for (int i = 0; i < size; i++) { final ShortcutInfo newShortcut = newShortcuts.get(i); @@ -2094,7 +2113,7 @@ public class ShortcutService extends IShortcutService.Stub { verifyStates(); - return true; + callback.complete(true); } @Override @@ -2159,15 +2178,17 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut, - IntentSender resultIntent, int userId) { + public void requestPinShortcut(String packageName, ShortcutInfo shortcut, + IntentSender resultIntent, int userId, AndroidFuture callback) { Objects.requireNonNull(shortcut); + Objects.requireNonNull(callback); Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); - return requestPinItem(packageName, userId, shortcut, null, null, resultIntent); + callback.complete(requestPinItem(packageName, userId, shortcut, null, null, resultIntent)); } @Override - public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId) + public void createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId, + AndroidFuture callback) throws RemoteException { Objects.requireNonNull(shortcut); Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); @@ -2183,7 +2204,7 @@ public class ShortcutService extends IShortcutService.Stub { } verifyStates(); - return ret; + callback.complete(ret); } /** @@ -2440,8 +2461,9 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName, - @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { + public void getShortcuts(String packageName, + @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId, + AndroidFuture<ParceledListSlice<ShortcutInfo>> callback) { verifyCaller(packageName, userId); synchronized (mLock) { @@ -2457,16 +2479,16 @@ public class ShortcutService extends IShortcutService.Stub { | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0); - return getShortcutsWithQueryLocked( + callback.complete(getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, (ShortcutInfo si) -> - si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0); + si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0)); } } @Override - public ParceledListSlice<ShortcutManager.ShareShortcutInfo> getShareTargets(String packageName, - IntentFilter filter, @UserIdInt int userId) { + public void getShareTargets(String packageName, IntentFilter filter, @UserIdInt int userId, + AndroidFuture<ParceledListSlice> callback) { Preconditions.checkStringNotEmpty(packageName, "packageName"); Objects.requireNonNull(filter, "intentFilter"); @@ -2482,7 +2504,7 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutUser user = getUserShortcutsLocked(userId); user.forAllPackages(p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter))); - return new ParceledListSlice<>(shortcutInfoList); + callback.complete(new ParceledListSlice<>(shortcutInfoList)); } } @@ -3066,8 +3088,14 @@ public class ShortcutService extends IShortcutService.Stub { @Override public List<ShortcutManager.ShareShortcutInfo> getShareTargets( @NonNull String callingPackage, @NonNull IntentFilter intentFilter, int userId) { - return ShortcutService.this.getShareTargets( - callingPackage, intentFilter, userId).getList(); + final AndroidFuture<ParceledListSlice> future = new AndroidFuture<>(); + ShortcutService.this.getShareTargets( + callingPackage, intentFilter, userId, future); + try { + return future.get().getList(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } @Override @@ -3114,7 +3142,8 @@ public class ShortcutService extends IShortcutService.Stub { if (doCache) { if (si.isLongLived()) { - si.addFlags(cacheFlags); + sp.mutateShortcut(si.getId(), si, + shortcut -> shortcut.addFlags(cacheFlags)); if (changedShortcuts == null) { changedShortcuts = new ArrayList<>(1); } @@ -3125,7 +3154,8 @@ public class ShortcutService extends IShortcutService.Stub { } } else { ShortcutInfo removed = null; - si.clearFlags(cacheFlags); + sp.mutateShortcut(si.getId(), si, shortcut -> + shortcut.clearFlags(cacheFlags)); if (!si.isDynamic() && !si.isCached()) { removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); } @@ -3487,6 +3517,22 @@ public class ShortcutService extends IShortcutService.Stub { } }; + private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems + // in odrder during saveToXml(), it could lead to shortcuts missing when shutdown. + // We need it so that it can finish up saving before shutdown. + synchronized (mLock) { + if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { + mHandler.removeCallbacks(mSaveDirtyInfoRunner); + saveDirtyInfo(); + } + mShutdown.set(true); + } + } + }; + /** * Called when a user is unlocked. * - Check all known packages still exist, and otherwise perform cleanup. diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 6cbc47fb59c4..ec784d0211dd 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -185,6 +185,9 @@ class ShortcutUser { public ShortcutPackage removePackage(@NonNull String packageName) { final ShortcutPackage removed = mPackages.remove(packageName); + if (removed != null) { + removed.removeShortcuts(); + } mService.cleanupBitmapsForPackage(mUserId, packageName); return removed; @@ -330,7 +333,10 @@ class ShortcutUser { if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) { if (isNewApp) { - mPackages.remove(packageName); + final ShortcutPackage sp = mPackages.remove(packageName); + if (sp != null) { + sp.removeShortcuts(); + } } } } @@ -454,6 +460,7 @@ class ShortcutUser { case ShortcutPackage.TAG_ROOT: { final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( s, ret, parser, fromBackup); + shortcuts.restoreParsedShortcuts(false); // Don't use addShortcut(), we don't need to save the icon. ret.mPackages.put(shortcuts.getPackageName(), shortcuts); @@ -488,6 +495,7 @@ class ShortcutUser { final ShortcutPackage sp = ShortcutPackage.loadFromFile(s, ret, f, fromBackup); if (sp != null) { ret.mPackages.put(sp.getPackageName(), sp); + sp.restoreParsedShortcuts(false); } }); @@ -570,6 +578,7 @@ class ShortcutUser { Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored." + " Existing non-manifeset shortcuts will be overwritten."); } + sp.restoreParsedShortcuts(true); addPackage(sp); restoredPackages[0]++; restoredShortcuts[0] += sp.getShortcutCount(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 871576e7c795..8283ac668d5e 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1544,7 +1544,7 @@ public class UserManagerService extends IUserManager.Stub { public String getUserName() { final int callingUid = Binder.getCallingUid(); if (!hasManageOrCreateUsersPermission() - || hasPermissionGranted( + && !hasPermissionGranted( android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid)) { throw new SecurityException("You need MANAGE_USERS or CREATE_USERS or " + "GET_ACCOUNTS_PRIVILEGED permissions to: get user name"); diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS index e05ef482ec08..8c1a90c13513 100644 --- a/services/core/java/com/android/server/pm/permission/OWNERS +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -1,9 +1,7 @@ -zhanghai@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS + per-file DefaultPermissionGrantPolicy.java = hackbod@android.com per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com -per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com per-file DefaultPermissionGrantPolicy.java = toddke@google.com per-file DefaultPermissionGrantPolicy.java = yamasani@google.com per-file DefaultPermissionGrantPolicy.java = patb@google.com -per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com -per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index e3cf67c34dad..bf2b3c7f491f 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -84,7 +84,7 @@ public class DomainVerificationCollector { @NonNull public ArraySet<String> collectAllWebDomains(@NonNull AndroidPackage pkg) { - return collectDomains(pkg, false); + return collectDomains(pkg, false /* checkAutoVerify */, true /* valid */); } /** @@ -92,20 +92,29 @@ public class DomainVerificationCollector { * IntentFilter#getAutoVerify()} == true. */ @NonNull - public ArraySet<String> collectAutoVerifyDomains(@NonNull AndroidPackage pkg) { - return collectDomains(pkg, true); + public ArraySet<String> collectValidAutoVerifyDomains(@NonNull AndroidPackage pkg) { + return collectDomains(pkg, true /* checkAutoVerify */, true /* valid */); + } + + /** + * Returns all the domains that are configured to be auto verified, but aren't actually valid + * HTTP domains, per {@link #DOMAIN_NAME_WITH_WILDCARD}. + */ + @NonNull + public ArraySet<String> collectInvalidAutoVerifyDomains(@NonNull AndroidPackage pkg) { + return collectDomains(pkg, true /* checkAutoVerify */, false /* valid */); } @NonNull private ArraySet<String> collectDomains(@NonNull AndroidPackage pkg, - boolean checkAutoVerify) { + boolean checkAutoVerify, boolean valid) { boolean restrictDomains = DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS); if (restrictDomains) { - return collectDomainsInternal(pkg, checkAutoVerify); + return collectDomainsInternal(pkg, checkAutoVerify, valid); } else { - return collectDomainsLegacy(pkg, checkAutoVerify); + return collectDomainsLegacy(pkg, checkAutoVerify, valid); } } @@ -113,10 +122,10 @@ public class DomainVerificationCollector { * @see #RESTRICT_DOMAINS */ private ArraySet<String> collectDomainsLegacy(@NonNull AndroidPackage pkg, - boolean checkAutoVerify) { + boolean checkAutoVerify, boolean valid) { if (!checkAutoVerify) { // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2 - return collectDomainsInternal(pkg, false); + return collectDomainsInternal(pkg, false /* checkAutoVerify */, true /* valid */); } List<ParsedActivity> activities = pkg.getActivities(); @@ -157,7 +166,7 @@ public class DomainVerificationCollector { int authorityCount = intent.countDataAuthorities(); for (int index = 0; index < authorityCount; index++) { String host = intent.getDataAuthority(index).getHost(); - if (isValidHost(host)) { + if (isValidHost(host) == valid) { totalSize += byteSizeOf(host); underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); @@ -174,7 +183,7 @@ public class DomainVerificationCollector { * @see #RESTRICT_DOMAINS */ private ArraySet<String> collectDomainsInternal(@NonNull AndroidPackage pkg, - boolean checkAutoVerify) { + boolean checkAutoVerify, boolean valid) { ArraySet<String> domains = new ArraySet<>(); int totalSize = 0; boolean underMaxSize = true; @@ -214,7 +223,7 @@ public class DomainVerificationCollector { int authorityCount = intent.countDataAuthorities(); for (int index = 0; index < authorityCount && underMaxSize; index++) { String host = intent.getDataAuthority(index).getHost(); - if (isValidHost(host)) { + if (isValidHost(host) == valid) { totalSize += byteSizeOf(host); underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); 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 b3108c58a11e..b61fd8d633f6 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 @@ -33,9 +33,9 @@ import android.util.SparseArray; import com.android.internal.util.CollectionUtils; import com.android.server.pm.PackageSetting; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import java.util.Arrays; import java.util.function.Function; @@ -107,7 +107,7 @@ public class DomainVerificationDebug { reusedMap.clear(); reusedMap.putAll(pkgState.getStateMap()); - ArraySet<String> declaredDomains = mCollector.collectAutoVerifyDomains(pkg); + ArraySet<String> declaredDomains = mCollector.collectValidAutoVerifyDomains(pkg); int declaredSize = declaredDomains.size(); for (int declaredIndex = 0; declaredIndex < declaredSize; declaredIndex++) { String domain = declaredDomains.valueAt(declaredIndex); @@ -132,6 +132,17 @@ public class DomainVerificationDebug { } writer.increaseIndent(); + final ArraySet<String> invalidDomains = mCollector.collectInvalidAutoVerifyDomains(pkg); + if (!invalidDomains.isEmpty()) { + writer.println("Invalid autoVerify domains:"); + writer.increaseIndent(); + int size = invalidDomains.size(); + for (int index = 0; index < size; index++) { + writer.println(invalidDomains.valueAt(index)); + } + writer.decreaseIndent(); + } + writer.println("Domain verification state:"); writer.increaseIndent(); int stateSize = reusedMap.size(); @@ -158,8 +169,8 @@ public class DomainVerificationDebug { } ArraySet<String> allWebDomains = mCollector.collectAllWebDomains(pkg); - SparseArray<DomainVerificationUserState> userStates = - pkgState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> userStates = + pkgState.getUserStates(); if (userId == UserHandle.USER_ALL) { int size = userStates.size(); if (size == 0) { @@ -167,13 +178,13 @@ public class DomainVerificationDebug { wasHeaderPrinted); } else { for (int index = 0; index < size; index++) { - DomainVerificationUserState userState = userStates.valueAt(index); + DomainVerificationInternalUserState userState = userStates.valueAt(index); printState(writer, pkgState, userState.getUserId(), userState, reusedSet, allWebDomains, wasHeaderPrinted); } } } else { - DomainVerificationUserState userState = userStates.get(userId); + DomainVerificationInternalUserState userState = userStates.get(userId); printState(writer, pkgState, userId, userState, reusedSet, allWebDomains, wasHeaderPrinted); } @@ -181,8 +192,9 @@ public class DomainVerificationDebug { boolean printState(@NonNull IndentingPrintWriter writer, @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId, - @Nullable DomainVerificationUserState userState, @NonNull ArraySet<String> reusedSet, - @NonNull ArraySet<String> allWebDomains, boolean wasHeaderPrinted) { + @Nullable DomainVerificationInternalUserState userState, + @NonNull ArraySet<String> reusedSet, @NonNull ArraySet<String> allWebDomains, + boolean wasHeaderPrinted) { reusedSet.clear(); reusedSet.addAll(allWebDomains); if (userState != null) { 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 ed37fa0da01f..1721a18f4f60 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 @@ -132,6 +132,21 @@ public class DomainVerificationEnforcer { /** * Enforced when mutating user selection state inside an exposed API method. */ + public boolean assertApprovedUserStateQuerent(int callingUid, @UserIdInt int callingUserId, + @NonNull String packageName, @UserIdInt int targetUserId) throws SecurityException { + if (callingUserId != targetUserId) { + mContext.enforcePermission( + Manifest.permission.INTERACT_ACROSS_USERS, + Binder.getCallingPid(), callingUid, + "Caller is not allowed to edit other users"); + } + + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); + } + + /** + * Enforced when mutating user selection state inside an exposed API method. + */ public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId, @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException { if (callingUserId != targetUserId) { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java index c787356f342c..4bad1020e945 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java @@ -32,7 +32,6 @@ import com.android.server.pm.SettingsXml; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.Map; /** * Reads and writes the old {@link android.content.pm.IntentFilterVerificationInfo} so that it can 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 9e22d82910df..0c2b4c547dae 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 @@ -48,7 +48,7 @@ import java.util.Set; import java.util.UUID; import java.util.function.Function; -public interface DomainVerificationManagerInternal extends DomainVerificationManager { +public interface DomainVerificationManagerInternal { UUID DISABLED_ID = new UUID(0, 0); @@ -69,8 +69,8 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan * during the legacy transition period. * * TODO(b/177923646): The legacy values can be removed once the Settings API changes are - * shipped. These values are not stable, so just deleting the constant and shifting others is - * fine. + * shipped. These values are not stable, so just deleting the constant and shifting others is + * fine. */ int APPROVAL_LEVEL_LEGACY_ASK = 1; @@ -84,14 +84,15 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan /** * The app has been chosen by the user through - * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit - * choice to use this app to open an unverified domain. + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}, + * indicating an explicit choice to use this app to open an unverified domain. */ int APPROVAL_LEVEL_SELECTION = 2; /** * The app is approved through the digital asset link statement being hosted at the domain - * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by + * it is capturing. This is set through + * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)} by * the domain verification agent on device. */ int APPROVAL_LEVEL_VERIFIED = 3; @@ -102,7 +103,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan * declares against the digital asset link statements before allowing it to be installed. * * The user is still able to disable instant app link handling through - * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. + * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, boolean)}. */ int APPROVAL_LEVEL_INSTANT_APP = 4; @@ -122,7 +123,17 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan APPROVAL_LEVEL_VERIFIED, APPROVAL_LEVEL_INSTANT_APP }) - @interface ApprovalLevel{} + @interface ApprovalLevel { + } + + /** @see DomainVerificationManager#getDomainVerificationInfo(String) */ + @Nullable + @RequiresPermission(anyOf = { + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION + }) + DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) + throws NameNotFoundException; /** * Generate a new domain set ID to be used for attaching new packages. @@ -173,9 +184,9 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan /** * Migrates verification state from a previous install to a new one. It is expected that the * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from - * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the - * assumption that the new package will pass the same server side config as the previous - * package, as they have matching signatures. + * {@link #generateNewId()}. This will preserve {@link DomainVerificationManager#STATE_SUCCESS} + * domains under the assumption that the new package will pass the same server side config as + * the previous package, as they have matching signatures. * <p> * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal * lock. This should never be called from within the domain verification classes themselves. @@ -229,8 +240,10 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan * tag has already been entered. * <p> * This is <b>only</b> for restore, and will override package states, ignoring if their {@link - * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains marked - * as success verify against the server correctly, although the verification agent may decide to + * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains + * marked + * as success verify against the server correctly, although the verification agent may decide + * to * re-verify them when it gets the chance. */ /* @@ -310,6 +323,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan */ @ApprovalLevel int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, + @NonNull List<ResolveInfo> candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId); /** 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 6f2810785c60..a7a52e0cd10c 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 @@ -23,29 +23,29 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.verify.domain.DomainOwner; 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.DomainVerificationManagerImpl; -import android.content.pm.verify.domain.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.ServiceSpecificException; import java.util.List; import java.util.UUID; -class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { +public class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { @NonNull - private DomainVerificationService mService; + private final DomainVerificationService mService; - DomainVerificationManagerStub(DomainVerificationService service) { + public DomainVerificationManagerStub(DomainVerificationService service) { mService = service; } @NonNull @Override - public List<String> getValidVerificationPackageNames() { + public List<String> queryValidVerificationPackageNames() { try { - return mService.getValidVerificationPackageNames(); + return mService.queryValidVerificationPackageNames(); } catch (Exception e) { throw rethrow(e); } @@ -95,10 +95,10 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { @Nullable @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( + public DomainVerificationUserState getDomainVerificationUserState( String packageName, @UserIdInt int userId) { try { - return mService.getDomainVerificationUserSelection(packageName, userId); + return mService.getDomainVerificationUserState(packageName, userId); } catch (Exception e) { throw rethrow(e); } @@ -117,13 +117,13 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { private RuntimeException rethrow(Exception exception) throws RuntimeException { if (exception instanceof InvalidDomainSetException) { - int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET; + int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET; packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16; return new ServiceSpecificException(packedErrorCode, ((InvalidDomainSetException) exception).getPackageName()); } else if (exception instanceof NameNotFoundException) { return new ServiceSpecificException( - DomainVerificationManagerImpl.ERROR_NAME_NOT_FOUND); + DomainVerificationManager.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/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java index c864b2937f6b..abb8d2fb6e1e 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java @@ -27,9 +27,9 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import com.android.server.pm.SettingsXml; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import org.xmlpull.v1.XmlPullParserException; @@ -157,7 +157,7 @@ public class DomainVerificationPersistence { UUID id = UUID.fromString(idString); final ArrayMap<String, Integer> stateMap = new ArrayMap<>(); - final SparseArray<DomainVerificationUserState> userStates = new SparseArray<>(); + final SparseArray<DomainVerificationInternalUserState> userStates = new SparseArray<>(); SettingsXml.ChildSection child = section.children(); while (child.moveToNext()) { @@ -176,10 +176,10 @@ public class DomainVerificationPersistence { } private static void readUserStates(@NonNull SettingsXml.ReadSection section, - @NonNull SparseArray<DomainVerificationUserState> userStates) { + @NonNull SparseArray<DomainVerificationInternalUserState> userStates) { SettingsXml.ChildSection child = section.children(); while (child.moveToNext(TAG_USER_STATE)) { - DomainVerificationUserState userState = createUserStateFromXml(child); + DomainVerificationInternalUserState userState = createUserStateFromXml(child); if (userState != null) { userStates.put(userState.getUserId(), userState); } @@ -205,12 +205,12 @@ public class DomainVerificationPersistence { .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS, pkgState.isHasAutoVerifyDomains())) { writeStateMap(parentSection, pkgState.getStateMap()); - writeUserStates(parentSection, pkgState.getUserSelectionStates()); + writeUserStates(parentSection, pkgState.getUserStates()); } } private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection, - @NonNull SparseArray<DomainVerificationUserState> states) throws IOException { + @NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException { int size = states.size(); if (size == 0) { return; @@ -245,7 +245,7 @@ public class DomainVerificationPersistence { * entered. */ @Nullable - public static DomainVerificationUserState createUserStateFromXml( + public static DomainVerificationInternalUserState createUserStateFromXml( @NonNull SettingsXml.ReadSection section) { int userId = section.getInt(ATTR_USER_ID); if (userId == -1) { @@ -260,7 +260,7 @@ public class DomainVerificationPersistence { readEnabledHosts(child, enabledHosts); } - return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling); + return new DomainVerificationInternalUserState(userId, enabledHosts, allowLinkHandling); } private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section, @@ -275,7 +275,7 @@ public class DomainVerificationPersistence { } public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection, - @NonNull DomainVerificationUserState userState) throws IOException { + @NonNull DomainVerificationInternalUserState userState) throws IOException { try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATE) .attribute(ATTR_USER_ID, userState.getUserId()) 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 b58c1ff374d5..e85bbe41f747 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 @@ -34,8 +34,9 @@ import android.content.pm.parsing.component.ParsedActivity; 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.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.UserHandle; import android.util.ArrayMap; @@ -55,9 +56,9 @@ import com.android.server.SystemService; import com.android.server.compat.PlatformCompat; import com.android.server.pm.PackageSetting; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable; @@ -208,8 +209,7 @@ public class DomainVerificationService extends SystemService } @NonNull - @Override - public List<String> getValidVerificationPackageNames() { + public List<String> queryValidVerificationPackageNames() { mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy); List<String> packageNames = new ArrayList<>(); synchronized (mLock) { @@ -256,7 +256,7 @@ public class DomainVerificationService extends SystemService Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap()); // TODO(b/159952358): Should the domain list be cached? - ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg); + ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); if (domains.isEmpty()) { return null; } @@ -272,7 +272,6 @@ public class DomainVerificationService extends SystemService } } - @Override public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, int state) throws InvalidDomainSetException, NameNotFoundException { if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) { @@ -314,7 +313,7 @@ public class DomainVerificationService extends SystemService int size = verifiedDomains.size(); for (int index = 0; index < size; index++) { - removeUserSelectionsForDomain(verifiedDomains.get(index)); + removeUserStatesForDomain(verifiedDomains.get(index)); } } @@ -354,7 +353,8 @@ public class DomainVerificationService extends SystemService validDomains.clear(); - ArraySet<String> autoVerifyDomains = mCollector.collectAutoVerifyDomains(pkg); + ArraySet<String> autoVerifyDomains = + mCollector.collectValidAutoVerifyDomains(pkg); if (domains == null) { validDomains.addAll(autoVerifyDomains); } else { @@ -379,9 +379,9 @@ public class DomainVerificationService extends SystemService AndroidPackage pkg = pkgSetting.getPkg(); if (domains == null) { - domains = mCollector.collectAutoVerifyDomains(pkg); + domains = mCollector.collectValidAutoVerifyDomains(pkg); } else { - domains.retainAll(mCollector.collectAutoVerifyDomains(pkg)); + domains.retainAll(mCollector.collectValidAutoVerifyDomains(pkg)); } setDomainVerificationStatusInternal(pkgState, state, domains); @@ -400,12 +400,12 @@ public class DomainVerificationService extends SystemService } } - private void removeUserSelectionsForDomain(@NonNull String domain) { + private void removeUserStatesForDomain(@NonNull String domain) { synchronized (mLock) { final int size = mAttachedPkgStates.size(); for (int index = 0; index < size; index++) { DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); - SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> array = pkgState.getUserStates(); int arraySize = array.size(); for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { array.valueAt(arrayIndex).removeHost(domain); @@ -414,13 +414,6 @@ public class DomainVerificationService extends SystemService } } - @Override - public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, - boolean allowed) throws NameNotFoundException { - setDomainVerificationLinkHandlingAllowed(packageName, allowed, - mConnection.getCallingUserId()); - } - public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed, @UserIdInt int userId) throws NameNotFoundException { if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), @@ -433,7 +426,7 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - pkgState.getOrCreateUserSelectionState(userId) + pkgState.getOrCreateUserState(userId) .setLinkHandlingAllowed(allowed); } @@ -451,11 +444,11 @@ public class DomainVerificationService extends SystemService DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex); if (userId == UserHandle.USER_ALL) { for (int aUserId : mConnection.getAllUserIds()) { - pkgState.getOrCreateUserSelectionState(aUserId) + pkgState.getOrCreateUserState(aUserId) .setLinkHandlingAllowed(allowed); } } else { - pkgState.getOrCreateUserSelectionState(userId) + pkgState.getOrCreateUserState(userId) .setLinkHandlingAllowed(allowed); } } @@ -467,7 +460,7 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - pkgState.getOrCreateUserSelectionState(userId) + pkgState.getOrCreateUserState(userId) .setLinkHandlingAllowed(allowed); } } @@ -475,14 +468,6 @@ public class DomainVerificationService extends SystemService mConnection.scheduleWriteSettings(); } - @Override - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, - @NonNull Set<String> domains, boolean enabled) - throws InvalidDomainSetException, NameNotFoundException { - setDomainVerificationUserSelection(domainSetId, domains, enabled, - mConnection.getCallingUserId()); - } - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId) throws InvalidDomainSetException, NameNotFoundException { @@ -499,7 +484,8 @@ public class DomainVerificationService extends SystemService DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, false /* forAutoVerify */, callingUid, userId); - DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); + 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. @@ -539,8 +525,8 @@ public class DomainVerificationService extends SystemService continue; } - DomainVerificationUserState approvedUserState = - approvedPkgState.getUserSelectionState(userId); + DomainVerificationInternalUserState approvedUserState = + approvedPkgState.getUserState(userId); if (approvedUserState == null) { continue; } @@ -622,8 +608,8 @@ public class DomainVerificationService extends SystemService if (userId == UserHandle.USER_ALL) { for (int aUserId : mConnection.getAllUserIds()) { - DomainVerificationUserState userState = - pkgState.getOrCreateUserSelectionState(aUserId); + DomainVerificationInternalUserState userState = + pkgState.getOrCreateUserState(aUserId); if (enabled) { userState.addHosts(domains); } else { @@ -631,7 +617,8 @@ public class DomainVerificationService extends SystemService } } } else { - DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); + DomainVerificationInternalUserState userState = + pkgState.getOrCreateUserState(userId); if (enabled) { userState.addHosts(domains); } else { @@ -642,17 +629,9 @@ public class DomainVerificationService extends SystemService @Nullable @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( - @NonNull String packageName) throws NameNotFoundException { - return getDomainVerificationUserSelection(packageName, - mConnection.getCallingUserId()); - } - - @Nullable - @Override - public DomainVerificationUserSelection getDomainVerificationUserSelection( + public DomainVerificationUserState getDomainVerificationUserState( @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException { - if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), + if (!mEnforcer.assertApprovedUserStateQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), packageName, userId)) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } @@ -672,7 +651,7 @@ public class DomainVerificationService extends SystemService Map<String, Integer> domains = new ArrayMap<>(webDomainsSize); ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); - DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); + DomainVerificationInternalUserState userState = pkgState.getUserState(userId); Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts(); for (int index = 0; index < webDomainsSize; index++) { @@ -681,11 +660,11 @@ public class DomainVerificationService extends SystemService int domainState; if (state != null && DomainVerificationManager.isStateVerified(state)) { - domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED; + domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED; } else if (enabledHosts.contains(host)) { - domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED; + domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED; } else { - domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE; + domainState = DomainVerificationUserState.DOMAIN_STATE_NONE; } domains.put(host, domainState); @@ -693,17 +672,11 @@ public class DomainVerificationService extends SystemService boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed(); - return new DomainVerificationUserSelection(pkgState.getId(), packageName, + return new DomainVerificationUserState(pkgState.getId(), packageName, UserHandle.of(userId), linkHandlingAllowed, domains); } } - @NonNull - @Override - public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { - return getOwnersForDomain(domain, mConnection.getCallingUserId()); - } - public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) { mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), userId); @@ -794,7 +767,7 @@ public class DomainVerificationService extends SystemService AndroidPackage newPkg = newPkgSetting.getPkg(); ArrayMap<String, Integer> newStateMap = new ArrayMap<>(); - SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>(); + SparseArray<DomainVerificationInternalUserState> newUserStates = new SparseArray<>(); if (oldPkgState == null || oldPkg == null || newPkg == null) { // Should be impossible, but to be safe, continue with a new blank state instead @@ -811,7 +784,8 @@ public class DomainVerificationService extends SystemService } ArrayMap<String, Integer> oldStateMap = oldPkgState.getStateMap(); - ArraySet<String> newAutoVerifyDomains = mCollector.collectAutoVerifyDomains(newPkg); + ArraySet<String> newAutoVerifyDomains = + mCollector.collectValidAutoVerifyDomains(newPkg); int newDomainsSize = newAutoVerifyDomains.size(); for (int newDomainsIndex = 0; newDomainsIndex < newDomainsSize; newDomainsIndex++) { @@ -836,21 +810,22 @@ public class DomainVerificationService extends SystemService } } - SparseArray<DomainVerificationUserState> oldUserStates = - oldPkgState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> oldUserStates = + oldPkgState.getUserStates(); int oldUserStatesSize = oldUserStates.size(); if (oldUserStatesSize > 0) { - ArraySet<String> newWebDomains = mCollector.collectAutoVerifyDomains(newPkg); + ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg); for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize; oldUserStatesIndex++) { int userId = oldUserStates.keyAt(oldUserStatesIndex); - DomainVerificationUserState oldUserState = oldUserStates.valueAt( + DomainVerificationInternalUserState oldUserState = oldUserStates.valueAt( oldUserStatesIndex); ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts(); ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts); newEnabledHosts.retainAll(newWebDomains); - DomainVerificationUserState newUserState = new DomainVerificationUserState( - userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed()); + DomainVerificationInternalUserState newUserState = + new DomainVerificationInternalUserState(userId, newEnabledHosts, + oldUserState.isLinkHandlingAllowed()); newUserStates.put(userId, newUserState); } } @@ -893,7 +868,7 @@ public class DomainVerificationService extends SystemService } AndroidPackage pkg = newPkgSetting.getPkg(); - ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg); + ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); boolean hasAutoVerifyDomains = !domains.isEmpty(); boolean isPendingOrRestored = pkgState != null; if (isPendingOrRestored) { @@ -924,7 +899,7 @@ public class DomainVerificationService extends SystemService webDomains = mCollector.collectAllWebDomains(pkg); } - pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains); + pkgState.getOrCreateUserState(userId).addHosts(webDomains); } } @@ -1157,7 +1132,7 @@ public class DomainVerificationService extends SystemService } AndroidPackage pkg = pkgSetting.getPkg(); ArraySet<String> declaredDomains = forAutoVerify - ? mCollector.collectAutoVerifyDomains(pkg) + ? mCollector.collectValidAutoVerifyDomains(pkg) : mCollector.collectAllWebDomains(pkg); if (domains.retainAll(declaredDomains)) { @@ -1289,11 +1264,11 @@ public class DomainVerificationService extends SystemService } } - applyImmutableState(pkgState, mCollector.collectAutoVerifyDomains(pkg)); + applyImmutableState(pkgState, mCollector.collectValidAutoVerifyDomains(pkg)); } @Override - public void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId) { + public void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId) { mEnforcer.assertInternal(mConnection.getCallingUid()); synchronized (mLock) { if (packageNames == null) { @@ -1494,9 +1469,11 @@ public class DomainVerificationService extends SystemService @Override public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, + @NonNull List<ResolveInfo> candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { String packageName = pkgSetting.getName(); - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, + resolveInfoFlags)) { if (DEBUG_APPROVAL) { debugApproval(packageName, intent, userId, false, "not valid intent"); } @@ -1541,7 +1518,7 @@ public class DomainVerificationService extends SystemService return APPROVAL_LEVEL_NONE; } - DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); + DomainVerificationInternalUserState userState = pkgState.getUserState(userId); if (userState != null && !userState.isLinkHandlingAllowed()) { if (DEBUG_APPROVAL) { @@ -1557,7 +1534,7 @@ public class DomainVerificationService extends SystemService // To allow an instant app to immediately open domains after being installed by the // user, auto approve them for any declared autoVerify domains. if (pkgSetting.getInstantApp(userId) - && mCollector.collectAutoVerifyDomains(pkg).contains(host)) { + && mCollector.collectValidAutoVerifyDomains(pkg).contains(host)) { return APPROVAL_LEVEL_INSTANT_APP; } } 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 a8e937cf2b90..f3d1dbb1f6ad 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 @@ -29,9 +29,9 @@ import android.util.TypedXmlSerializer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; -import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import org.xmlpull.v1.XmlPullParserException; @@ -216,21 +216,22 @@ class DomainVerificationSettings { } } - SparseArray<DomainVerificationUserState> oldSelectionStates = - oldState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> oldSelectionStates = + oldState.getUserStates(); - SparseArray<DomainVerificationUserState> newSelectionStates = - newState.getUserSelectionStates(); + SparseArray<DomainVerificationInternalUserState> newSelectionStates = + newState.getUserStates(); - DomainVerificationUserState newUserState = newSelectionStates.get(UserHandle.USER_SYSTEM); + DomainVerificationInternalUserState newUserState = + newSelectionStates.get(UserHandle.USER_SYSTEM); if (newUserState != null) { ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts(); - DomainVerificationUserState oldUserState = + DomainVerificationInternalUserState oldUserState = oldSelectionStates.get(UserHandle.USER_SYSTEM); boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed(); if (oldUserState == null) { - oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM, + oldUserState = new DomainVerificationInternalUserState(UserHandle.USER_SYSTEM, newEnabledHosts, linkHandlingAllowed); oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState); } else { 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 d083d11cb2e2..94767f555574 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 @@ -24,7 +24,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; -import android.content.pm.verify.domain.DomainVerificationUserSelection; +import android.content.pm.verify.domain.DomainVerificationUserState; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; @@ -118,7 +118,7 @@ public class DomainVerificationShell { case "set-app-links": return runSetAppLinks(commandHandler); case "set-app-links-user-selection": - return runSetAppLinksUserSelection(commandHandler); + return runSetAppLinksUserState(commandHandler); case "set-app-links-allowed": return runSetAppLinksAllowed(commandHandler); } @@ -193,7 +193,7 @@ public class DomainVerificationShell { } // pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>... - private boolean runSetAppLinksUserSelection(@NonNull BasicShellCommandHandler commandHandler) { + private boolean runSetAppLinksUserState(@NonNull BasicShellCommandHandler commandHandler) { Integer userId = null; String packageName = null; @@ -224,7 +224,7 @@ public class DomainVerificationShell { return false; } - userId = translateUserId(userId, "runSetAppLinksUserSelection"); + userId = translateUserId(userId, "runSetAppLinksUserState"); String enabledString = commandHandler.getNextArgRequired(); @@ -326,7 +326,7 @@ public class DomainVerificationShell { } if (userId != null) { - mCallback.clearUserSelections(packageNames, userId); + mCallback.clearUserStates(packageNames, userId); } else { mCallback.clearDomainVerificationState(packageNames); } @@ -457,10 +457,10 @@ public class DomainVerificationShell { throws PackageManager.NameNotFoundException; /** - * @see DomainVerificationManager#getDomainVerificationUserSelection(String) + * @see DomainVerificationManager#getDomainVerificationUserState(String) */ @Nullable - DomainVerificationUserSelection getDomainVerificationUserSelection( + DomainVerificationUserState getDomainVerificationUserState( @NonNull String packageName, @UserIdInt int userId) throws PackageManager.NameNotFoundException; @@ -486,7 +486,7 @@ public class DomainVerificationShell { * Reset all the user selections for the given package names, or all package names if null * is provided. */ - void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId); + void clearUserStates(@Nullable List<String> packageNames, @UserIdInt int userId); /** * Broadcast a verification request for the given package names, or all package names if 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 783aff6ccb55..883bbad1bd2d 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,12 +22,17 @@ import android.content.Intent; 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; import com.android.server.compat.PlatformCompat; 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 { /** @@ -40,13 +45,61 @@ public final class DomainVerificationUtils { throw new NameNotFoundException("Package " + packageName + " unavailable"); } - public static boolean isDomainVerificationIntent(Intent intent, int resolveInfoFlags) { - if (!intent.isWebIntent() || !intent.hasCategory(Intent.CATEGORY_BROWSABLE)) { + public static boolean isDomainVerificationIntent(Intent intent, + @NonNull List<ResolveInfo> candidates, + @PackageManager.ResolveInfoFlags int resolveInfoFlags) { + if (!intent.isWebIntent()) { return false; } - return ((resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0) - || intent.hasCategory(Intent.CATEGORY_DEFAULT); + Set<String> categories = intent.getCategories(); + int categoriesSize = CollectionUtils.size(categories); + if (categoriesSize > 2) { + // Specifying at least one non-app-link category + return false; + } else if (categoriesSize == 2) { + // Check for explicit app link intent with exactly BROWSABLE && DEFAULT + return intent.hasCategory(Intent.CATEGORY_DEFAULT) + && 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; + } 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; + } } static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg, diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java index 8fbb33afb6ca..aa7407ce3fe8 100644 --- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java +++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java @@ -29,7 +29,7 @@ import java.util.Set; * when a web URL Intent is sent and the application is the highest priority for that domain. */ @DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false) -public class DomainVerificationUserState { +public class DomainVerificationInternalUserState { @UserIdInt private final int mUserId; @@ -43,32 +43,32 @@ public class DomainVerificationUserState { */ private boolean mLinkHandlingAllowed = true; - public DomainVerificationUserState(@UserIdInt int userId) { + public DomainVerificationInternalUserState(@UserIdInt int userId) { mUserId = userId; mEnabledHosts = new ArraySet<>(); } - public DomainVerificationUserState addHosts(@NonNull ArraySet<String> newHosts) { + public DomainVerificationInternalUserState addHosts(@NonNull ArraySet<String> newHosts) { mEnabledHosts.addAll(newHosts); return this; } - public DomainVerificationUserState addHosts(@NonNull Set<String> newHosts) { + public DomainVerificationInternalUserState addHosts(@NonNull Set<String> newHosts) { mEnabledHosts.addAll(newHosts); return this; } - public DomainVerificationUserState removeHost(String host) { + public DomainVerificationInternalUserState removeHost(String host) { mEnabledHosts.remove(host); return this; } - public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) { + public DomainVerificationInternalUserState removeHosts(@NonNull ArraySet<String> newHosts) { mEnabledHosts.removeAll(newHosts); return this; } - public DomainVerificationUserState removeHosts(@NonNull Set<String> newHosts) { + public DomainVerificationInternalUserState removeHosts(@NonNull Set<String> newHosts) { mEnabledHosts.removeAll(newHosts); return this; } @@ -81,8 +81,7 @@ public class DomainVerificationUserState { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm - // /verify/domain/models/DomainVerificationUserState.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -90,7 +89,7 @@ public class DomainVerificationUserState { /** - * Creates a new DomainVerificationUserState. + * Creates a new DomainVerificationInternalUserState. * * @param enabledHosts * List of domains which have been enabled by the user. * @@ -98,7 +97,7 @@ public class DomainVerificationUserState { * Whether to allow this package to automatically open links by auto verification. */ @DataClass.Generated.Member - public DomainVerificationUserState( + public DomainVerificationInternalUserState( @UserIdInt int userId, @NonNull ArraySet<String> enabledHosts, boolean linkHandlingAllowed) { @@ -138,7 +137,7 @@ public class DomainVerificationUserState { * Whether to allow this package to automatically open links by auto verification. */ @DataClass.Generated.Member - public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) { + public @NonNull DomainVerificationInternalUserState setLinkHandlingAllowed( boolean value) { mLinkHandlingAllowed = value; return this; } @@ -149,7 +148,7 @@ public class DomainVerificationUserState { // You can override field toString logic by defining methods like: // String fieldNameToString() { ... } - return "DomainVerificationUserState { " + + return "DomainVerificationInternalUserState { " + "userId = " + mUserId + ", " + "enabledHosts = " + mEnabledHosts + ", " + "linkHandlingAllowed = " + mLinkHandlingAllowed + @@ -160,13 +159,13 @@ public class DomainVerificationUserState { @DataClass.Generated.Member public boolean equals(@android.annotation.Nullable Object o) { // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(DomainVerificationUserState other) { ... } + // boolean fieldNameEquals(DomainVerificationInternalUserState other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") - DomainVerificationUserState that = (DomainVerificationUserState) o; + DomainVerificationInternalUserState that = (DomainVerificationInternalUserState) o; //noinspection PointlessBooleanExpression return true && mUserId == that.mUserId @@ -188,10 +187,10 @@ public class DomainVerificationUserState { } @DataClass.Generated( - time = 1612894390039L, + time = 1614714563905L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java", - inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)") + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationInternalUserState.java", + inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHost(java.lang.String)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationInternalUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)") @Deprecated private void __metadata() {} 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 48099aa5382b..a089a6022735 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 @@ -19,7 +19,6 @@ package com.android.server.pm.verify.domain.models; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; import android.util.ArrayMap; import android.util.SparseArray; @@ -44,9 +43,9 @@ public class DomainVerificationPkgState { /** * Whether or not the package declares any autoVerify domains. This is separate from an empty - * check on the map itself, because an empty map means no response recorded, not necessarily no - * domains declared. When this is false, {@link #mStateMap} will be empty, but - * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to + * check on the map itself, because an empty map means no response recorded, not necessarily + * no domains declared. When this is false, {@link #mStateMap} will be empty, but + * {@link #mUserStates} may contain any domains the user has explicitly chosen to * allow this package to open, which may or may not be marked autoVerify. */ private final boolean mHasAutoVerifyDomains; @@ -62,7 +61,7 @@ public class DomainVerificationPkgState { private final ArrayMap<String, Integer> mStateMap; @NonNull - private final SparseArray<DomainVerificationUserState> mUserSelectionStates; + private final SparseArray<DomainVerificationInternalUserState> mUserStates; public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id, boolean hasAutoVerifyDomains) { @@ -70,16 +69,17 @@ public class DomainVerificationPkgState { } @Nullable - public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) { - return mUserSelectionStates.get(userId); + public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) { + return mUserStates.get(userId); } @Nullable - public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) { - DomainVerificationUserState userState = mUserSelectionStates.get(userId); + public DomainVerificationInternalUserState getOrCreateUserState( + @UserIdInt int userId) { + DomainVerificationInternalUserState userState = mUserStates.get(userId); if (userState == null) { - userState = new DomainVerificationUserState(userId); - mUserSelectionStates.put(userId, userState); + userState = new DomainVerificationInternalUserState(userId); + mUserStates.put(userId, userState); } return userState; } @@ -89,20 +89,20 @@ public class DomainVerificationPkgState { } public void removeUser(@UserIdInt int userId) { - mUserSelectionStates.remove(userId); + mUserStates.remove(userId); } public void removeAllUsers() { - mUserSelectionStates.clear(); + mUserStates.clear(); } - private int userSelectionStatesHashCode() { - return mUserSelectionStates.contentHashCode(); + private int userStatesHashCode() { + return mUserStates.contentHashCode(); } - private boolean userSelectionStatesEquals( - @NonNull SparseArray<DomainVerificationUserState> other) { - return mUserSelectionStates.contentEquals(other); + private boolean userStatesEquals( + @NonNull SparseArray<DomainVerificationInternalUserState> other) { + return mUserStates.contentEquals(other); } @@ -113,7 +113,7 @@ public class DomainVerificationPkgState { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -123,9 +123,15 @@ public class DomainVerificationPkgState { /** * Creates a new DomainVerificationPkgState. * + * @param hasAutoVerifyDomains + * Whether or not the package declares any autoVerify domains. This is separate from an empty + * check on the map itself, because an empty map means no response recorded, not necessarily + * no domains declared. When this is false, {@link #mStateMap} will be empty, but + * {@link #mUserStates} may contain any domains the user has explicitly chosen to + * allow this package to open, which may or may not be marked autoVerify. * @param stateMap * Map of domains to state integers. Only domains that are not set to the default value of - * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included. + * {@link DomainVerificationState#STATE_NO_RESPONSE} are included. * * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, * such as storing no state when the package is marked as a linked app in SystemConfig. @@ -136,7 +142,7 @@ public class DomainVerificationPkgState { @NonNull UUID id, boolean hasAutoVerifyDomains, @NonNull ArrayMap<String,Integer> stateMap, - @NonNull SparseArray<DomainVerificationUserState> userSelectionStates) { + @NonNull SparseArray<DomainVerificationInternalUserState> userStates) { this.mPackageName = packageName; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPackageName); @@ -147,9 +153,9 @@ public class DomainVerificationPkgState { this.mStateMap = stateMap; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mStateMap); - this.mUserSelectionStates = userSelectionStates; + this.mUserStates = userStates; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mUserSelectionStates); + NonNull.class, null, mUserStates); // onConstructed(); // You can define this method to get a callback } @@ -164,6 +170,13 @@ public class DomainVerificationPkgState { return mId; } + /** + * Whether or not the package declares any autoVerify domains. This is separate from an empty + * check on the map itself, because an empty map means no response recorded, not necessarily + * no domains declared. When this is false, {@link #mStateMap} will be empty, but + * {@link #mUserStates} may contain any domains the user has explicitly chosen to + * allow this package to open, which may or may not be marked autoVerify. + */ @DataClass.Generated.Member public boolean isHasAutoVerifyDomains() { return mHasAutoVerifyDomains; @@ -171,7 +184,7 @@ public class DomainVerificationPkgState { /** * Map of domains to state integers. Only domains that are not set to the default value of - * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included. + * {@link DomainVerificationState#STATE_NO_RESPONSE} are included. * * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, * such as storing no state when the package is marked as a linked app in SystemConfig. @@ -182,8 +195,8 @@ public class DomainVerificationPkgState { } @DataClass.Generated.Member - public @NonNull SparseArray<DomainVerificationUserState> getUserSelectionStates() { - return mUserSelectionStates; + public @NonNull SparseArray<DomainVerificationInternalUserState> getUserStates() { + return mUserStates; } @Override @@ -197,7 +210,7 @@ public class DomainVerificationPkgState { "id = " + mId + ", " + "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " + "stateMap = " + mStateMap + ", " + - "userSelectionStates = " + mUserSelectionStates + + "userStates = " + mUserStates + " }"; } @@ -218,7 +231,7 @@ public class DomainVerificationPkgState { && Objects.equals(mId, that.mId) && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains && Objects.equals(mStateMap, that.mStateMap) - && userSelectionStatesEquals(that.mUserSelectionStates); + && userStatesEquals(that.mUserStates); } @Override @@ -232,15 +245,15 @@ public class DomainVerificationPkgState { _hash = 31 * _hash + Objects.hashCode(mId); _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains); _hash = 31 * _hash + Objects.hashCode(mStateMap); - _hash = 31 * _hash + userSelectionStatesHashCode(); + _hash = 31 * _hash + userStatesHashCode(); return _hash; } @DataClass.Generated( - time = 1608234185474L, + time = 1614818362549L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState> mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userSelectionStatesHashCode()\nprivate boolean userSelectionStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java", + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userStatesHashCode()\nprivate boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} 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 a80406548719..18042af139a3 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 @@ -16,6 +16,9 @@ package com.android.server.pm.verify.domain.proxy; +import static android.os.PowerWhitelistManager.REASON_DOMAIN_VERIFICATION_V1; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -238,7 +241,8 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration(); mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(), mVerifierComponent.getPackageName(), allowListTimeout, - UserHandle.USER_SYSTEM, true, "domain verification agent"); + UserHandle.USER_SYSTEM, true, REASON_DOMAIN_VERIFICATION_V1, + "domain verification agent"); int size = verifications.size(); for (int index = 0; index < size; index++) { @@ -261,7 +265,9 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setTemporaryAppWhitelistDuration(allowListTimeout); + options.setTemporaryAppAllowlist(allowListTimeout, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_DOMAIN_VERIFICATION_V1, ""); mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle()); } } @@ -270,7 +276,7 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { private String buildHostsString(@NonNull AndroidPackage pkg) { // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion, // not the version of the verification agent on device. - ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg); + ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); // v1 doesn't handle wildcard domains, so transform them here to the root StringBuilder builder = new StringBuilder(); diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java index 1ef06036021e..2ba17d3a86a5 100644 --- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java +++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java @@ -16,6 +16,9 @@ package com.android.server.pm.verify.domain.proxy; +import static android.os.PowerWhitelistManager.REASON_DOMAIN_VERIFICATION_V2; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.BroadcastOptions; @@ -69,11 +72,14 @@ public class DomainVerificationProxyV2 implements DomainVerificationProxy { final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration(); final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setTemporaryAppWhitelistDuration(allowListTimeout); + options.setTemporaryAppAllowlist(allowListTimeout, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_DOMAIN_VERIFICATION_V2, ""); mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(), mVerifierComponent.getPackageName(), allowListTimeout, - UserHandle.USER_SYSTEM, true, "domain verification agent"); + UserHandle.USER_SYSTEM, true, REASON_DOMAIN_VERIFICATION_V2, + "domain verification agent"); Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION) .setComponent(mVerifierComponent) diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index ab404dbad910..a0771c0cc8d5 100644 --- a/services/core/java/com/android/server/policy/ShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,24 @@ package com.android.server.policy; +import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; +import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.LongSparseArray; +import android.util.Slog; import android.util.SparseArray; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import com.android.internal.policy.IShortcutService; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -39,8 +45,9 @@ import java.io.IOException; * Manages quick launch shortcuts by: * <li> Keeping the local copy in sync with the database (this is an observer) * <li> Returning a shortcut-matching intent to clients + * <li> Returning particular kind of application intent by special key. */ -class ShortcutManager { +class ModifierShortcutManager { private static final String TAG = "ShortcutManager"; private static final String TAG_BOOKMARKS = "bookmarks"; @@ -52,12 +59,39 @@ class ShortcutManager { private static final String ATTRIBUTE_CATEGORY = "category"; private static final String ATTRIBUTE_SHIFT = "shift"; - private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>(); + private final SparseArray<ShortcutInfo> mIntentShortcuts = new SparseArray<>(); private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>(); + private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>(); + + /* Table of Application Launch keys. Maps from key codes to intent categories. + * + * These are special keys that are used to launch particular kinds of applications, + * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) + * usage page. We don't support quite that many yet... + */ + static SparseArray<String> sApplicationLaunchKeyCategories; + static { + sApplicationLaunchKeyCategories = new SparseArray<String>(); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); + } + private final Context mContext; - - public ShortcutManager(Context context) { + private boolean mSearchKeyShortcutPending = false; + private boolean mConsumeSearchKeyUp = true; + + ModifierShortcutManager(Context context) { mContext = context; loadShortcuts(); } @@ -70,19 +104,19 @@ class ShortcutManager { * <p> * This will first try an exact match (with modifiers), and then try a * match without modifiers (primary character on a key). - * + * * @param kcm The key character map of the device on which the key was pressed. * @param keyCode The key code. * @param metaState The meta state, omitting any modifiers that were used * to invoke the shortcut. * @return The intent that matches the shortcut, or null if not found. */ - public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { + private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { ShortcutInfo shortcut = null; // If the Shift key is pressed, then search for the shift shortcuts. boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; - SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts; + SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts; // First try the exact keycode (with modifiers). int shortcutChar = kcm.get(keyCode, metaState); @@ -176,7 +210,7 @@ class ShortcutManager { if (isShiftShortcut) { mShiftShortcuts.put(shortcutChar, shortcut); } else { - mShortcuts.put(shortcutChar, shortcut); + mIntentShortcuts.put(shortcutChar, shortcut); } } } catch (XmlPullParserException e) { @@ -186,11 +220,159 @@ class ShortcutManager { } } + void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) + throws RemoteException { + IShortcutService service = mShortcutKeyServices.get(shortcutCode); + if (service != null && service.asBinder().pingBinder()) { + throw new RemoteException("Key already exists."); + } + + mShortcutKeyServices.put(shortcutCode, shortcutService); + } + + /** + * Handle the shortcut to {@link IShortcutService} + * @param keyCode The key code of the event. + * @param metaState The meta key modifier state. + * @return True if invoked the shortcut, otherwise false. + */ + private boolean handleShortcutService(int keyCode, int metaState) { + long shortcutCode = keyCode; + if ((metaState & KeyEvent.META_CTRL_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_ALT_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_SHIFT_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE; + } + + if ((metaState & KeyEvent.META_META_ON) != 0) { + shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE; + } + + IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); + if (shortcutService != null) { + try { + shortcutService.notifyShortcutKeyPressed(shortcutCode); + } catch (RemoteException e) { + mShortcutKeyServices.delete(shortcutCode); + } + return true; + } + return false; + } + + /** + * Handle the shortcut to {@link Intent} + * + * @param kcm the {@link KeyCharacterMap} associated with the keyboard device. + * @param keyCode The key code of the event. + * @param metaState The meta key modifier state. + * @return True if invoked the shortcut, otherwise false. + */ + private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) { + // Shortcuts are invoked through Search+key, so intercept those here + // Any printing key that is chorded with Search should be consumed + // even if no shortcut was invoked. This prevents text from being + // inadvertently inserted when using a keyboard that has built-in macro + // shortcut keys (that emit Search+x) and some of them are not registered. + if (mSearchKeyShortcutPending) { + if (kcm.isPrintingKey(keyCode)) { + mConsumeSearchKeyUp = true; + mSearchKeyShortcutPending = false; + } else { + return false; + } + } else if ((metaState & KeyEvent.META_META_MASK) != 0) { + // Invoke shortcuts using Meta. + metaState &= ~KeyEvent.META_META_MASK; + } else { + // Handle application launch keys. + String category = sApplicationLaunchKeyCategories.get(keyCode); + if (category != null) { + Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping application launch key because " + + "the activity to which it is registered was not found: " + + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + "," + + " category=" + category, ex); + } + return true; + } else { + return false; + } + } + + final Intent shortcutIntent = getIntent(kcm, keyCode, metaState); + if (shortcutIntent != null) { + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping shortcut key combination because " + + "the activity to which it is registered was not found: " + + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode), ex); + } + return true; + } + return false; + } + + /** + * Handle the shortcut from {@link KeyEvent} + * + * @param event Description of the key event. + * @return True if invoked the shortcut, otherwise false. + */ + boolean interceptKey(KeyEvent event) { + if (event.getRepeatCount() != 0) { + return false; + } + + final int metaState = event.getModifiers(); + final int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_SEARCH) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + mSearchKeyShortcutPending = true; + mConsumeSearchKeyUp = false; + } else { + mSearchKeyShortcutPending = false; + if (mConsumeSearchKeyUp) { + mConsumeSearchKeyUp = false; + return true; + } + } + return false; + } + + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + if (handleIntentShortcut(kcm, keyCode, metaState)) { + return true; + } + + if (handleShortcutService(keyCode, metaState)) { + return true; + } + + return false; + } + private static final class ShortcutInfo { public final String title; public final Intent intent; - public ShortcutInfo(String title, Intent intent) { + ShortcutInfo(String title, Intent intent) { this.title = title; this.intent = intent; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1b192e43c7b8..047e3b362b7a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -165,7 +165,6 @@ import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; import android.util.Log; -import android.util.LongSparseArray; import android.util.MutableBoolean; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -320,29 +319,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ private boolean mKeyguardDrawnOnce; - /* Table of Application Launch keys. Maps from key codes to intent categories. - * - * These are special keys that are used to launch particular kinds of applications, - * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) - * usage page. We don't support quite that many yet... - */ - static SparseArray<String> sApplicationLaunchKeyCategories; - static { - sApplicationLaunchKeyCategories = new SparseArray<String>(); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); - sApplicationLaunchKeyCategories.append( - KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); - } - /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */ static final int WAITING_FOR_DRAWN_TIMEOUT = 1000; @@ -421,8 +397,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mSafeMode; private WindowState mKeyguardCandidate = null; - private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>(); - // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. // This is for car dock and this is updated from resource. private boolean mEnableCarDockHomeCapture = true; @@ -516,8 +490,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { Intent mCarDockIntent; Intent mDeskDockIntent; Intent mVrHeadsetHomeIntent; - boolean mSearchKeyShortcutPending; - boolean mConsumeSearchKeyUp; boolean mPendingMetaAction; boolean mPendingCapsLockToggle; @@ -578,7 +550,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int BRIGHTNESS_STEPS = 10; SettingsObserver mSettingsObserver; - ShortcutManager mShortcutManager; + ModifierShortcutManager mModifierShortcutManager; PowerManager.WakeLock mBroadcastWakeLock; PowerManager.WakeLock mPowerKeyWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; @@ -916,7 +888,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mSingleKeyGestureDetector.reset(); finishPowerKeyPress(); } - } private void finishPowerKeyPress() { @@ -1085,7 +1056,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, "Power - Long Press - Global Actions"); - showGlobalActionsInternal(); + showGlobalActions(); break; case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: @@ -1116,14 +1087,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void powerVeryLongPress() { switch (mVeryLongPressOnPowerBehavior) { - case VERY_LONG_PRESS_POWER_NOTHING: - break; - case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: - mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, - "Power - Very Long Press - Show Global Actions"); - showGlobalActionsInternal(); - break; + case VERY_LONG_PRESS_POWER_NOTHING: + break; + case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: + mPowerKeyHandled = true; + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + "Power - Very Long Press - Show Global Actions"); + showGlobalActions(); + break; } } @@ -1646,7 +1617,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); - mShortcutManager = new ShortcutManager(context); + mModifierShortcutManager = new ModifierShortcutManager(context); mUiMode = context.getResources().getInteger( com.android.internal.R.integer.config_defaultUiModeType); mHomeIntent = new Intent(Intent.ACTION_MAIN, null); @@ -1917,12 +1888,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onLongPress(long downTime) { - powerLongPress(downTime); + void onLongPress(long eventTime) { + powerLongPress(eventTime); } @Override - void onVeryLongPress(long downTime) { + void onVeryLongPress(long eventTime) { mActivityManagerInternal.prepareForPossibleShutdown(); powerVeryLongPress(); } @@ -2525,6 +2496,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingCapsLockToggle = false; } + if (isUserSetupComplete() && !keyguardOn) { + if (mModifierShortcutManager.interceptKey(event)) { + dismissKeyboardShortcutsMenu(); + mPendingMetaAction = false; + mPendingCapsLockToggle = false; + return key_consumed; + } + } + switch(keyCode) { case KeyEvent.KEYCODE_HOME: // First we always handle the home key here, so applications @@ -2550,20 +2530,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } break; - case KeyEvent.KEYCODE_SEARCH: - if (down) { - if (repeatCount == 0) { - mSearchKeyShortcutPending = true; - mConsumeSearchKeyUp = false; - } - } else { - mSearchKeyShortcutPending = false; - if (mConsumeSearchKeyUp) { - mConsumeSearchKeyUp = false; - return key_consumed; - } - } - return 0; case KeyEvent.KEYCODE_APP_SWITCH: if (!keyguardOn) { if (down && repeatCount == 0) { @@ -2771,114 +2737,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; } - // Shortcuts are invoked through Search+key, so intercept those here - // Any printing key that is chorded with Search should be consumed - // even if no shortcut was invoked. This prevents text from being - // inadvertently inserted when using a keyboard that has built-in macro - // shortcut keys (that emit Search+x) and some of them are not registered. - if (mSearchKeyShortcutPending) { - final KeyCharacterMap kcm = event.getKeyCharacterMap(); - if (kcm.isPrintingKey(keyCode)) { - mConsumeSearchKeyUp = true; - mSearchKeyShortcutPending = false; - if (down && repeatCount == 0 && !keyguardOn) { - Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState); - if (shortcutIntent != null) { - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(shortcutIntent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping shortcut key combination because " - + "the activity to which it is registered was not found: " - + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex); - } - } else { - Slog.i(TAG, "Dropping unregistered shortcut key combination: " - + "SEARCH+" + KeyEvent.keyCodeToString(keyCode)); - } - } - return key_consumed; - } - } - - // Invoke shortcuts using Meta. - if (down && repeatCount == 0 && !keyguardOn - && (metaState & KeyEvent.META_META_ON) != 0) { - final KeyCharacterMap kcm = event.getKeyCharacterMap(); - if (kcm.isPrintingKey(keyCode)) { - Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, - metaState & ~(KeyEvent.META_META_ON - | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON)); - if (shortcutIntent != null) { - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(shortcutIntent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping shortcut key combination because " - + "the activity to which it is registered was not found: " - + "META+" + KeyEvent.keyCodeToString(keyCode), ex); - } - return key_consumed; - } - } - } - - // Handle application launch keys. - if (down && repeatCount == 0 && !keyguardOn) { - String category = sApplicationLaunchKeyCategories.get(keyCode); - if (category != null) { - Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivityAsUser(intent, UserHandle.CURRENT); - dismissKeyboardShortcutsMenu(); - } catch (ActivityNotFoundException ex) { - Slog.w(TAG, "Dropping application launch key because " - + "the activity to which it is registered was not found: " - + "keyCode=" + keyCode + ", category=" + category, ex); - } - return key_consumed; - } - } - if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { return key_consumed; } - if (down) { - long shortcutCode = keyCode; - if (event.isCtrlPressed()) { - shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE; - } - - if (event.isAltPressed()) { - shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; - } - - if (event.isShiftPressed()) { - shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE; - } - - if (event.isMetaPressed()) { - shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE; - } - - IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); - if (shortcutService != null) { - try { - if (isUserSetupComplete()) { - shortcutService.notifyShortcutKeyPressed(shortcutCode); - } - } catch (RemoteException e) { - mShortcutKeyServices.delete(shortcutCode); - } - return key_consumed; - } - } - // Reserve all the META modifier combos for system behavior if ((metaState & KeyEvent.META_META_ON) != 0) { return key_consumed; @@ -3064,12 +2927,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) throws RemoteException { synchronized (mLock) { - IShortcutService service = mShortcutKeyServices.get(shortcutCode); - if (service != null && service.asBinder().pingBinder()) { - throw new RemoteException("Key already exists."); - } - - mShortcutKeyServices.put(shortcutCode, shortcutService); + mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService); } } diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 3dafb0ce21ef..cae209353361 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -78,8 +78,8 @@ public final class SingleKeyGestureDetector { * new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) { * int getMaxMultiPressCount() { // maximum multi press count. } * void onPress(long downTime) { // short press behavior. } - * void onLongPress() { // long press behavior. } - * void onVeryLongPress() { // very long press behavior. } + * void onLongPress(long eventTime) { // long press behavior. } + * void onVeryLongPress(long eventTime) { // very long press behavior. } * void onMultiPress(long downTime, int count) { // multi press behavior. } * }; * </pre> @@ -135,11 +135,11 @@ public final class SingleKeyGestureDetector { /** * Callback when long press has been detected. */ - void onLongPress(long downTime) {} + void onLongPress(long eventTime) {} /** * Callback when very long press has been detected. */ - void onVeryLongPress(long downTime) {} + void onVeryLongPress(long eventTime) {} @Override public String toString() { @@ -174,9 +174,9 @@ public final class SingleKeyGestureDetector { // same key down. if (mDownKeyCode == keyCode) { if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0 - && !mHandledByLongPress) { + && mActiveRule.supportLongPress() && !mHandledByLongPress) { if (DEBUG) { - Log.i(TAG, "Long press Key " + KeyEvent.keyCodeToString(keyCode)); + Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode)); } mHandledByLongPress = true; mHandler.removeMessages(MSG_KEY_LONG_PRESS); @@ -192,7 +192,6 @@ public final class SingleKeyGestureDetector { if (DEBUG) { Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode)); } - reset(); } mDownKeyCode = keyCode; @@ -203,6 +202,9 @@ public final class SingleKeyGestureDetector { for (int index = 0; index < count; index++) { final SingleKeyRule rule = mRules.get(index); if (rule.shouldInterceptKey(keyCode)) { + if (DEBUG) { + Log.i(TAG, "Intercept key by rule " + rule); + } mActiveRule = rule; break; } @@ -254,6 +256,7 @@ public final class SingleKeyGestureDetector { if (mHandledByLongPress) { mHandledByLongPress = false; + mKeyPressCounter = 0; return true; } @@ -261,6 +264,9 @@ public final class SingleKeyGestureDetector { if (event.getKeyCode() == mActiveRule.mKeyCode) { // Directly trigger short press when max count is 1. if (mActiveRule.getMaxMultiPressCount() == 1) { + if (DEBUG) { + Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode())); + } mActiveRule.onPress(downTime); return true; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index bc117094dd68..29adde37ab3b 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -5073,7 +5073,7 @@ public final class PowerManagerService extends SystemService } @Override // Binder call - public void userActivity(long eventTime, int event, int flags) { + public void userActivity(int displayId, long eventTime, int event, int flags) { final long now = mClock.uptimeMillis(); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) != PackageManager.PERMISSION_GRANTED diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS index 31e3549d9111..dafdf0f8075c 100644 --- a/services/core/java/com/android/server/role/OWNERS +++ b/services/core/java/com/android/server/role/OWNERS @@ -1,5 +1 @@ -svetoslavganov@google.com -zhanghai@google.com -evanseverson@google.com -eugenesusla@google.com -ntmyren@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/services/core/java/com/android/server/servicewatcher/OWNERS b/services/core/java/com/android/server/servicewatcher/OWNERS new file mode 100644 index 000000000000..ced619f05f1d --- /dev/null +++ b/services/core/java/com/android/server/servicewatcher/OWNERS @@ -0,0 +1,5 @@ +# Bug component: 25692 + +sooniln@google.com +wyattriley@google.com + diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java index 8a2894c84cc4..5d49663209b7 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.servicewatcher; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_NOT_FOREGROUND; @@ -52,6 +52,7 @@ import com.android.internal.annotations.Immutable; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.FgThread; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java index de8823c4b7f3..6366280e1762 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -39,6 +39,7 @@ import android.media.soundtrigger_middleware.RecognitionStatus; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundModelType; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; +import android.os.HidlMemory; import android.os.HidlMemoryUtil; import android.os.ParcelFileDescriptor; @@ -199,18 +200,7 @@ class ConversionUtil { hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type); hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid); hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid); - - // Extract a dup of the underlying FileDescriptor out of aidlModel.data without changing - // the original. - FileDescriptor fd = new FileDescriptor(); - try { - ParcelFileDescriptor dup = aidlModel.data.dup(); - fd.setInt$(dup.detachFd()); - hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(fd, aidlModel.dataSize); - } catch (IOException e) { - throw new RuntimeException(e); - } - + hidlModel.data = parcelFileDescriptorToHidlMemory(aidlModel.data, aidlModel.dataSize); return hidlModel; } @@ -425,4 +415,31 @@ class ConversionUtil { } return aidlCapabilities; } + + /** + * Convert an AIDL representation of a shared memory block (ParcelFileDescriptor + size) to the + * HIDL representation (HidlMemory). Will not change the incoming data or any ownership + * semantics, but rather duplicate the underlying FD. + * + * @param data The incoming memory block. May be null if dataSize is 0. + * @param dataSize The number of bytes in the block. + * @return A HidlMemory representation of the memory block. Will be non-null even for an empty + * block. + */ + private static @NonNull + HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) { + if (dataSize > 0) { + // Extract a dup of the underlying FileDescriptor out of data. + FileDescriptor fd = new FileDescriptor(); + try { + ParcelFileDescriptor dup = data.dup(); + fd.setInt$(dup.detachFd()); + return HidlMemoryUtil.fileDescriptorToHidlMemory(fd, dataSize); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0); + } + } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java index ebe9733e5d55..212f81f72b24 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java @@ -35,8 +35,7 @@ import java.util.TimerTask; * HAL whenever they expire. */ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 { - // TODO(b/166328980): Reduce this to 1000 as soon as HAL is fixed. - private static final long TIMEOUT_MS = 10000; + private static final long TIMEOUT_MS = 3000; private static final String TAG = "SoundTriggerHw2Watchdog"; private final @NonNull diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java index 43047d1ebaaa..e05c468186ed 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java @@ -62,7 +62,9 @@ public class ValidationUtil { } validateUuid(model.uuid); validateUuid(model.vendorUuid); - Objects.requireNonNull(model.data); + if (model.dataSize > 0) { + Objects.requireNonNull(model.data); + } } static void validatePhraseModel(@Nullable PhraseSoundModel model) { 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 6aa7c8a290c1..7ed7a592a972 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -3759,13 +3759,15 @@ public class StatsPullAtomService extends SystemService { ConnectivityManager.NetworkCallback { @Override public void onAvailable(Network network) { - FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId, + FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, + network.getNetId(), FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__CONNECTED); } @Override public void onLost(Network network) { - FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, network.netId, + FrameworkStatsLog.write(FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED, + network.getNetId(), FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__DISCONNECTED); } } diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index d2b05c0914d7..a0e2286f72e2 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -42,8 +42,8 @@ import android.os.storage.StorageManagerInternal; import android.os.storage.StorageVolume; import android.service.storage.ExternalStorageService; import android.service.storage.IExternalStorageService; -import android.util.ArraySet; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; @@ -53,11 +53,13 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Controls the lifecycle of the {@link ActiveConnection} to an {@link ExternalStorageService} @@ -72,15 +74,17 @@ public final class StorageUserConnection { private final Context mContext; private final int mUserId; private final StorageSessionController mSessionController; + private final StorageManagerInternal mSmInternal; private final ActiveConnection mActiveConnection = new ActiveConnection(); - @GuardedBy("mLock") private final Map<String, Session> mSessions = new HashMap<>(); - @GuardedBy("mLock") private final Set<Integer> mUidsBlockedOnIo = new ArraySet<>(); + @GuardedBy("mSessionsLock") private final Map<String, Session> mSessions = new HashMap<>(); + @GuardedBy("mSessionsLock") private final SparseArray<Integer> mUidsBlockedOnIo = new SparseArray<>(); private final HandlerThread mHandlerThread; public StorageUserConnection(Context context, int userId, StorageSessionController controller) { mContext = Objects.requireNonNull(context); mUserId = Preconditions.checkArgumentNonnegative(userId); mSessionController = controller; + mSmInternal = LocalServices.getService(StorageManagerInternal.class); mHandlerThread = new HandlerThread("StorageUserConnectionThread-" + mUserId); mHandlerThread.start(); } @@ -152,9 +156,13 @@ public final class StorageUserConnection { */ public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason) throws ExternalStorageServiceException { + List<String> primarySessionIds = mSmInternal.getPrimaryVolumeIds(); synchronized (mSessionsLock) { for (String sessionId : mSessions.keySet()) { - mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason); + if (primarySessionIds.contains(sessionId)) { + mActiveConnection.notifyAnrDelayStarted(packageName, uid, tid, reason); + return; + } } } } @@ -201,8 +209,7 @@ public final class StorageUserConnection { return; } } - StorageManagerInternal sm = LocalServices.getService(StorageManagerInternal.class); - sm.resetUser(mUserId); + mSmInternal.resetUser(mUserId); } /** @@ -242,7 +249,8 @@ public final class StorageUserConnection { public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, @StorageManager.AppIoBlockedReason int reason) { synchronized (mSessionsLock) { - mUidsBlockedOnIo.add(uid); + int ioBlockedCounter = mUidsBlockedOnIo.get(uid, 0); + mUidsBlockedOnIo.put(uid, ++ioBlockedCounter); } } @@ -255,7 +263,12 @@ public final class StorageUserConnection { public void notifyAppIoResumed(String volumeUuid, int uid, int tid, @StorageManager.AppIoBlockedReason int reason) { synchronized (mSessionsLock) { - mUidsBlockedOnIo.remove(uid); + int ioBlockedCounter = mUidsBlockedOnIo.get(uid, 0); + if (ioBlockedCounter == 0) { + mUidsBlockedOnIo.remove(uid); + } else { + mUidsBlockedOnIo.put(uid, --ioBlockedCounter); + } } } @@ -317,6 +330,23 @@ public final class StorageUserConnection { } } + private void asyncBestEffort(Consumer<IExternalStorageService> consumer) { + synchronized (mLock) { + if (mRemoteFuture == null) { + Slog.w(TAG, "Dropping async request service is not bound"); + return; + } + + IExternalStorageService service = mRemoteFuture.getNow(null); + if (service == null) { + Slog.w(TAG, "Dropping async request service is not connected"); + return; + } + + consumer.accept(service); + } + } + private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception { CompletableFuture<Void> opFuture = new CompletableFuture<>(); RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture)); @@ -401,13 +431,13 @@ public final class StorageUserConnection { public void notifyAnrDelayStarted(String packgeName, int uid, int tid, int reason) throws ExternalStorageServiceException { - try { - waitForAsyncVoid((service, callback) -> - service.notifyAnrDelayStarted(packgeName, uid, tid, reason, callback)); - } catch (Exception e) { - throw new ExternalStorageServiceException("Failed to notify ANR delay started: " - + packgeName, e); - } + asyncBestEffort(service -> { + try { + service.notifyAnrDelayStarted(packgeName, uid, tid, reason); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify ANR delay started", e); + } + }); } private void setResult(Bundle result, CompletableFuture<Void> future) { diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index 531c62c5b4e9..0b51488280e0 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -39,8 +39,8 @@ import android.service.timezone.TimeZoneProviderSuggestion; import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; -import com.android.server.ServiceWatcher; -import com.android.server.ServiceWatcher.BoundService; +import com.android.server.servicewatcher.ServiceWatcher; +import com.android.server.servicewatcher.ServiceWatcher.BoundService; import java.util.Objects; import java.util.function.Predicate; diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java index 5772dea287fc..e54b40eb334c 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java @@ -16,6 +16,8 @@ package com.android.server.uri; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Intent; import android.content.pm.ProviderInfo; import android.net.Uri; @@ -58,6 +60,19 @@ public interface UriGrantsManagerInternal { void grantUriPermissionUncheckedFromIntent( NeededUriGrants needed, UriPermissionOwner owner); + /** + * Creates a new stateful object to track uri permission grants. This is needed to maintain + * state when managing grants via {@link UriGrantsManagerService#grantUriPermissionFromOwner}, + * {@link #revokeUriPermissionFromOwner}, etc. + * + * @param name A name for the object. This is only used for logcat/dumpsys logging, so there + * are no uniqueness or other requirements, but it is recommended to make the + * name sufficiently readable so that the relevant code area can be determined + * easily when this name shows up in a bug report. + * @return An opaque owner token for tracking uri permission grants. + * @see UriPermissionOwner + * @see UriGrantsManagerService + */ IBinder newUriPermissionOwner(String name); /** @@ -74,33 +89,39 @@ public interface UriGrantsManagerInternal { */ void removeUriPermissionsForPackage( String packageName, int userHandle, boolean persistable, boolean targetOnly); + /** - * Remove any {@link UriPermission} associated with the owner whose values match the given - * filtering parameters. - * - * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}. - * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris. - * @param mode The modes (as a bitmask) to revoke. - * @param userId The userId in which the uri is to be resolved. + * Like {@link #revokeUriPermissionFromOwner(IBinder, Uri, int, int, String, int)} but applies + * to all target packages and all target users. */ - void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId); + void revokeUriPermissionFromOwner(@NonNull IBinder token, @Nullable Uri uri, int mode, + int userId); /** * Remove any {@link UriPermission} associated with the owner whose values match the given * filtering parameters. * * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}. - * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris. - * @param mode The modes (as a bitmask) to revoke. - * @param userId The userId in which the uri is to be resolved. - * @param targetPkg Calling package name to match, or {@code null} to apply to all packages. - * @param targetUserId Calling user to match, or {@link UserHandle#USER_ALL} to apply to all - * users. + * @param uri The content uri for which the permission grant should be revoked. This uri + * must NOT contain an embedded userId; use + * {@link android.content.ContentProvider#getUriWithoutUserId(Uri)} if needed. + * This param may be {@code null} to revoke grants for all uris tracked by the + * provided owner token. + * @param mode The modes (as a bitmask) to revoke. See + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, etc. + * @param userId The userId in which the given uri is to be resolved. If the {@code uri} + * param is {@code null}, this param is ignored since permissions for all + * uris will be revoked. + * @param targetPkg Target package name to match (app that received the grant), or + * {@code null} to apply to all packages. + * @param targetUserId Target user to match (userId of the app that received the grant), or + * {@link UserHandle#USER_ALL} to apply to all users. */ - void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId, - String targetPkg, int targetUserId); + void revokeUriPermissionFromOwner(@NonNull IBinder token, @Nullable Uri uri, int mode, + int userId, @Nullable String targetPkg, int targetUserId); boolean checkAuthorityGrants( int callingUid, ProviderInfo cpi, int userId, boolean checkUser); + void dump(PrintWriter pw, boolean dumpAll, String dumpPackage); } diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index dcc15999d882..44545ed4898a 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -33,17 +33,13 @@ import static android.os.Process.ROOT_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Process.myUid; -import static com.android.internal.util.XmlUtils.readBooleanAttribute; -import static com.android.internal.util.XmlUtils.readIntAttribute; -import static com.android.internal.util.XmlUtils.readLongAttribute; import static com.android.internal.util.XmlUtils.writeBooleanAttribute; -import static com.android.internal.util.XmlUtils.writeIntAttribute; -import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.server.uri.UriGrantsManagerService.H.PERSIST_URI_GRANTS_MSG; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -82,7 +78,6 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; import com.android.server.IoThread; import com.android.server.LocalServices; @@ -94,9 +89,7 @@ import com.google.android.collect.Maps; import libcore.io.IoUtils; -import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; @@ -104,7 +97,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -211,6 +203,21 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } } + /** + * Grant uri permissions to the specified app. + * + * @param token An opaque owner token for tracking the permissions. See + * {@link UriGrantsManagerInternal#newUriPermissionOwner}. + * @param fromUid The uid of the grantor app that has permissions to the uri. Permissions + * will be granted on behalf of this app. + * @param targetPkg The package name of the grantor app that has permissions to the uri. + * Permissions will be granted on behalf of this app. + * @param uri The uri for which permissions should be granted. This uri must NOT contain an + * embedded userId; use {@link ContentProvider#getUriWithoutUserId(Uri)} if needed. + * @param modeFlags The modes to grant. See {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, etc. + * @param sourceUserId The userId in which the uri is to be resolved. + * @param targetUserId The userId of the target app to receive the grant. + */ @Override public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, Uri uri, final int modeFlags, int sourceUserId, int targetUserId) { @@ -219,12 +226,11 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } /** - * @param uri This uri must NOT contain an embedded userId. - * @param sourceUserId The userId in which the uri is to be resolved. - * @param targetUserId The userId of the app that receives the grant. + * See {@link #grantUriPermissionFromOwner(IBinder, int, String, Uri, int, int, int)}. */ - private void grantUriPermissionFromOwnerUnlocked(IBinder token, int fromUid, String targetPkg, - Uri uri, final int modeFlags, int sourceUserId, int targetUserId) { + private void grantUriPermissionFromOwnerUnlocked(@NonNull IBinder token, int fromUid, + @NonNull String targetPkg, @NonNull Uri uri, final int modeFlags, + int sourceUserId, int targetUserId) { targetUserId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), targetUserId, false, ALLOW_FULL_ONLY, "grantUriPermissionFromOwner", null); diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index b6ddd93af3b8..b2db9f5af07e 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -65,7 +65,7 @@ public class UnderlyingNetworkTracker { @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback(); @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; - private boolean mIsRunning = true; + private boolean mIsQuitting = false; @Nullable private UnderlyingNetworkRecord mCurrentRecord; @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; @@ -151,7 +151,7 @@ public class UnderlyingNetworkTracker { mVcnContext.ensureRunningOnLooperThread(); // Don't bother re-filing NetworkRequests if this Tracker has been torn down. - if (!mIsRunning) { + if (mIsQuitting) { return; } @@ -205,7 +205,7 @@ public class UnderlyingNetworkTracker { } mCellBringupCallbacks.clear(); - mIsRunning = false; + mIsQuitting = true; } /** Returns whether the currently selected Network matches the given network. */ diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 6ad30b544257..9d39c67d27fb 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -16,6 +16,8 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; + import static com.android.server.VcnManagementService.VDBG; import android.annotation.NonNull; @@ -84,6 +86,13 @@ public class Vcn extends Handler { */ private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2; + /** + * A GatewayConnection owned by this VCN quit. + * + * @param obj VcnGatewayConnectionConfig + */ + private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3; + /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; @@ -208,6 +217,9 @@ public class Vcn extends Handler { case MSG_EVENT_SUBSCRIPTIONS_CHANGED: handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj); break; + case MSG_EVENT_GATEWAY_CONNECTION_QUIT: + handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj); + break; case MSG_CMD_TEARDOWN: handleTeardown(); break; @@ -263,7 +275,7 @@ public class Vcn extends Handler { // If preexisting VcnGatewayConnection(s) satisfy request, return for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) { - if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { if (VDBG) { Slog.v( getLogTag(), @@ -278,7 +290,7 @@ public class Vcn extends Handler { // up for (VcnGatewayConnectionConfig gatewayConnectionConfig : mConfig.getGatewayConnectionConfigs()) { - if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { Slog.v( getLogTag(), "Bringing up new VcnGatewayConnection for request " + request.requestId); @@ -289,12 +301,21 @@ public class Vcn extends Handler { mSubscriptionGroup, mLastSnapshot, gatewayConnectionConfig, - new VcnGatewayStatusCallbackImpl()); + new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig)); mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); } } } + private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) { + Slog.v(getLogTag(), "VcnGatewayConnection quit: " + config); + mVcnGatewayConnections.remove(config); + + // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied + // start a new GatewayConnection) + mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); + } + private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { mLastSnapshot = snapshot; @@ -305,9 +326,10 @@ public class Vcn extends Handler { } } - private boolean requestSatisfiedByGatewayConnectionConfig( + private boolean isRequestSatisfiedByGatewayConnectionConfig( @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); + builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); for (int cap : config.getAllExposedCapabilities()) { builder.addCapability(cap); } @@ -339,9 +361,23 @@ public class Vcn extends Handler { @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage); + + /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */ + void onQuit(); } private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback { + public final VcnGatewayConnectionConfig mGatewayConnectionConfig; + + VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) { + mGatewayConnectionConfig = gatewayConnectionConfig; + } + + @Override + public void onQuit() { + sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig)); + } + @Override public void onEnteredSafeMode() { sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE)); diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 06748a3aa2d1..6bc9978a0731 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -168,6 +168,8 @@ public class VcnGatewayConnection extends StateMachine { private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: "; private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST = "Underlying Network lost"; + private static final String DISCONNECT_REASON_NETWORK_AGENT_UNWANTED = + "NetworkAgent was unwanted"; private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel"; private static final int TOKEN_ALL = Integer.MIN_VALUE; @@ -379,13 +381,16 @@ public class VcnGatewayConnection extends StateMachine { /** The reason why the disconnect was requested. */ @NonNull public final String reason; - EventDisconnectRequestedInfo(@NonNull String reason) { + public final boolean shouldQuit; + + EventDisconnectRequestedInfo(@NonNull String reason, boolean shouldQuit) { this.reason = Objects.requireNonNull(reason); + this.shouldQuit = shouldQuit; } @Override public int hashCode() { - return Objects.hash(reason); + return Objects.hash(reason, shouldQuit); } @Override @@ -395,7 +400,7 @@ public class VcnGatewayConnection extends StateMachine { } final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other; - return reason.equals(rhs.reason); + return reason.equals(rhs.reason) && shouldQuit == rhs.shouldQuit; } } @@ -488,8 +493,14 @@ public class VcnGatewayConnection extends StateMachine { */ @NonNull private final VcnWakeLock mWakeLock; - /** Running state of this VcnGatewayConnection. */ - private boolean mIsRunning = true; + /** + * Whether the VcnGatewayConnection is in the process of irreversibly quitting. + * + * <p>This variable is false for the lifecycle of the VcnGatewayConnection, until a command to + * teardown has been received. This may be flipped due to events such as the Network becoming + * unwanted, the owning VCN entering safe mode, or an irrecoverable internal failure. + */ + private boolean mIsQuitting = false; /** * The token used by the primary/current/active session. @@ -622,10 +633,8 @@ public class VcnGatewayConnection extends StateMachine { * <p>Once torn down, this VcnTunnel CANNOT be started again. */ public void teardownAsynchronously() { - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */); // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this // is also called asynchronously when a NetworkAgent becomes unwanted @@ -646,6 +655,8 @@ public class VcnGatewayConnection extends StateMachine { cancelSafeModeAlarm(); mUnderlyingNetworkTracker.teardown(); + + mGatewayStatusCallback.onQuit(); } /** @@ -693,7 +704,7 @@ public class VcnGatewayConnection extends StateMachine { private void acquireWakeLock() { mVcnContext.ensureRunningOnLooperThread(); - if (mIsRunning) { + if (!mIsQuitting) { mWakeLock.acquire(); } } @@ -892,7 +903,7 @@ public class VcnGatewayConnection extends StateMachine { TOKEN_ALL, 0 /* arg2 */, new EventDisconnectRequestedInfo( - DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */)); mDisconnectRequestAlarm = createScheduledAlarm( DISCONNECT_REQUEST_ALARM, @@ -909,7 +920,8 @@ public class VcnGatewayConnection extends StateMachine { // Cancel any existing disconnect due to previous loss of underlying network removeEqualMessages( EVENT_DISCONNECT_REQUESTED, - new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + new EventDisconnectRequestedInfo( + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */)); } private void setRetryTimeoutAlarm(long delay) { @@ -1041,11 +1053,8 @@ public class VcnGatewayConnection extends StateMachine { enterState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo( - DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */); } } @@ -1083,11 +1092,8 @@ public class VcnGatewayConnection extends StateMachine { processStateMsg(msg); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo( - DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */); } // Attempt to release the WakeLock - only possible if the Handler queue is empty @@ -1104,11 +1110,8 @@ public class VcnGatewayConnection extends StateMachine { exitState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessageAndAcquireWakeLock( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo( - DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */); } } @@ -1141,11 +1144,11 @@ public class VcnGatewayConnection extends StateMachine { } } - protected void handleDisconnectRequested(String msg) { + protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) { // TODO(b/180526152): notify VcnStatusCallback for Network loss - Slog.v(TAG, "Tearing down. Cause: " + msg); - mIsRunning = false; + Slog.v(TAG, "Tearing down. Cause: " + info.reason); + mIsQuitting = info.shouldQuit; teardownNetwork(); @@ -1177,7 +1180,7 @@ public class VcnGatewayConnection extends StateMachine { private class DisconnectedState extends BaseState { @Override protected void enterState() { - if (!mIsRunning) { + if (mIsQuitting) { quitNow(); // Ignore all queued events; cleanup is complete. } @@ -1200,9 +1203,11 @@ public class VcnGatewayConnection extends StateMachine { } break; case EVENT_DISCONNECT_REQUESTED: - mIsRunning = false; + if (((EventDisconnectRequestedInfo) msg.obj).shouldQuit) { + mIsQuitting = true; - quitNow(); + quitNow(); + } break; default: logUnhandledMessage(msg); @@ -1284,10 +1289,11 @@ public class VcnGatewayConnection extends StateMachine { break; case EVENT_DISCONNECT_REQUESTED: + EventDisconnectRequestedInfo info = ((EventDisconnectRequestedInfo) msg.obj); + mIsQuitting = info.shouldQuit; teardownNetwork(); - String reason = ((EventDisconnectRequestedInfo) msg.obj).reason; - if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) { + if (info.reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) { // TODO(b/180526152): notify VcnStatusCallback for Network loss // Will trigger EVENT_SESSION_CLOSED immediately. @@ -1300,7 +1306,7 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_SESSION_CLOSED: mIkeSession = null; - if (mIsRunning && mUnderlying != null) { + if (!mIsQuitting && mUnderlying != null) { transitionTo(mSkipRetryTimeout ? mConnectingState : mRetryTimeoutState); } else { teardownNetwork(); @@ -1391,7 +1397,7 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mConnectedState); break; case EVENT_DISCONNECT_REQUESTED: - handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); break; case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: mGatewayStatusCallback.onEnteredSafeMode(); @@ -1438,6 +1444,7 @@ public class VcnGatewayConnection extends StateMachine { mVcnContext.getVcnNetworkProvider()) { @Override public void unwanted() { + Slog.d(TAG, "NetworkAgent was unwanted"); teardownAsynchronously(); } @@ -1471,7 +1478,7 @@ public class VcnGatewayConnection extends StateMachine { @NonNull IpSecTransform transform, int direction) { try { - // TODO(b/180163196): Set underlying network of tunnel interface + tunnelIface.setUnderlyingNetwork(underlyingNetwork); // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); @@ -1577,7 +1584,7 @@ public class VcnGatewayConnection extends StateMachine { setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig); break; case EVENT_DISCONNECT_REQUESTED: - handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); break; case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: mGatewayStatusCallback.onEnteredSafeMode(); @@ -1682,7 +1689,7 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mConnectingState); break; case EVENT_DISCONNECT_REQUESTED: - handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); break; case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: mGatewayStatusCallback.onEnteredSafeMode(); @@ -1905,13 +1912,13 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) - boolean isRunning() { - return mIsRunning; + boolean isQuitting() { + return mIsQuitting; } @VisibleForTesting(visibility = Visibility.PRIVATE) - void setIsRunning(boolean isRunning) { - mIsRunning = isRunning; + void setIsQuitting(boolean isQuitting) { + mIsQuitting = isQuitting; } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -1924,6 +1931,14 @@ public class VcnGatewayConnection extends StateMachine { mIkeSession = session; } + @VisibleForTesting(visibility = Visibility.PRIVATE) + void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) { + sendMessageAndAcquireWakeLock( + EVENT_DISCONNECT_REQUESTED, + TOKEN_ALL, + new EventDisconnectRequestedInfo(reason, shouldQuit)); + } + private IkeSessionParams buildIkeParams() { // TODO: Implement this once IkeSessionParams is persisted return null; diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java index bfeec011a2c9..a90969599159 100644 --- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -67,9 +67,7 @@ public class VcnNetworkProvider extends NetworkProvider { mListeners.add(listener); // Send listener all cached requests - for (NetworkRequestEntry entry : mRequests.values()) { - notifyListenerForEvent(listener, entry); - } + resendAllRequests(listener); } /** Unregisters the specified listener from receiving future NetworkRequests. */ @@ -78,6 +76,14 @@ public class VcnNetworkProvider extends NetworkProvider { mListeners.remove(listener); } + /** Sends all cached NetworkRequest(s) to the specified listener. */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void resendAllRequests(@NonNull NetworkRequestListener listener) { + for (NetworkRequestEntry entry : mRequests.values()) { + notifyListenerForEvent(listener, entry); + } + } + private void notifyListenerForEvent( @NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) { listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId); diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java index 685dce4683d7..96f84dc65e1d 100644 --- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java +++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import android.annotation.Nullable; import android.content.Context; import android.hardware.input.InputManager; import android.os.CombinedVibrationEffect; @@ -33,7 +34,11 @@ final class InputDeviceDelegate implements InputManager.InputDeviceListener { private final Object mLock = new Object(); private final Handler mHandler; - private final InputManager mInputManager; + private final Context mContext; + + @GuardedBy("mLock") + @Nullable + private InputManager mInputManager; @GuardedBy("mLock") private final SparseArray<VibratorManager> mInputDeviceVibrators = new SparseArray<>(); @@ -47,7 +52,13 @@ final class InputDeviceDelegate implements InputManager.InputDeviceListener { InputDeviceDelegate(Context context, Handler handler) { mHandler = handler; - mInputManager = context.getSystemService(InputManager.class); + mContext = context; + } + + public void onSystemReady() { + synchronized (mLock) { + mInputManager = mContext.getSystemService(InputManager.class); + } } @Override @@ -116,6 +127,10 @@ final class InputDeviceDelegate implements InputManager.InputDeviceListener { */ public boolean updateInputDeviceVibrators(boolean vibrateInputDevices) { synchronized (mLock) { + if (mInputManager == null) { + // Ignore update, service not loaded yet so change cannot be applied. + return false; + } if (vibrateInputDevices == mShouldVibrateInputDevices) { // No need to update if settings haven't changed. return false; @@ -150,6 +165,10 @@ final class InputDeviceDelegate implements InputManager.InputDeviceListener { private void updateInputDevice(int deviceId) { synchronized (mLock) { + if (mInputManager == null) { + // Ignore update, service not loaded yet so change cannot be applied. + return; + } if (!mShouldVibrateInputDevices) { // No need to keep this device vibrator if setting is off. return; diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 607da3ce6fe2..e84ee672bf0f 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -120,7 +120,9 @@ final class Vibration { if (newEffect.equals(mEffect)) { return; } - mOriginalEffect = mEffect; + if (mOriginalEffect == null) { + mOriginalEffect = mEffect; + } mEffect = newEffect; } diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 334129d6bde9..4a07c1ac1e39 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IUidObserver; import android.content.Context; @@ -57,8 +58,6 @@ final class VibrationSettings { private final Object mLock = new Object(); private final Context mContext; - private final Vibrator mVibrator; - private final AudioManager mAudioManager; private final SettingsObserver mSettingObserver; @VisibleForTesting final UidObserver mUidObserver; @@ -68,6 +67,13 @@ final class VibrationSettings { private final SparseArray<VibrationEffect> mFallbackEffects; @GuardedBy("mLock") + @Nullable + private Vibrator mVibrator; + @GuardedBy("mLock") + @Nullable + private AudioManager mAudioManager; + + @GuardedBy("mLock") private boolean mVibrateInputDevices; @GuardedBy("mLock") private boolean mVibrateWhenRinging; @@ -86,22 +92,9 @@ final class VibrationSettings { VibrationSettings(Context context, Handler handler) { mContext = context; - mVibrator = context.getSystemService(Vibrator.class); - mAudioManager = context.getSystemService(AudioManager.class); mSettingObserver = new SettingsObserver(handler); mUidObserver = new UidObserver(); - registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); - registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING)); - registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.APPLY_RAMPING_RINGER)); - registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE)); - registerSettingsObserver( - Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY)); - registerSettingsObserver( - Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY)); - registerSettingsObserver( - Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY)); - VibrationEffect clickEffect = createEffectFromResource( com.android.internal.R.array.config_virtualKeyVibePattern); VibrationEffect doubleClickEffect = VibrationEffect.createWaveform( @@ -119,6 +112,15 @@ final class VibrationSettings { mFallbackEffects.put(VibrationEffect.EFFECT_TEXTURE_TICK, VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)); + // Update with current values from settings. + updateSettings(); + } + + public void onSystemReady() { + synchronized (mLock) { + mVibrator = mContext.getSystemService(Vibrator.class); + mAudioManager = mContext.getSystemService(AudioManager.class); + } try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, @@ -148,7 +150,18 @@ final class VibrationSettings { } }); - // Update with current values from settings. + registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); + registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING)); + registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.APPLY_RAMPING_RINGER)); + registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY)); + + // Update with newly loaded services. updateSettings(); } @@ -178,17 +191,21 @@ final class VibrationSettings { * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_* */ public int getDefaultIntensity(int usageHint) { - if (isRingtone(usageHint)) { - return mVibrator.getDefaultRingVibrationIntensity(); - } else if (isNotification(usageHint)) { - return mVibrator.getDefaultNotificationVibrationIntensity(); - } else if (isHapticFeedback(usageHint)) { - return mVibrator.getDefaultHapticFeedbackIntensity(); - } else if (isAlarm(usageHint)) { + if (isAlarm(usageHint)) { return Vibrator.VIBRATION_INTENSITY_HIGH; - } else { - return Vibrator.VIBRATION_INTENSITY_MEDIUM; } + synchronized (mLock) { + if (mVibrator != null) { + if (isRingtone(usageHint)) { + return mVibrator.getDefaultRingVibrationIntensity(); + } else if (isNotification(usageHint)) { + return mVibrator.getDefaultNotificationVibrationIntensity(); + } else if (isHapticFeedback(usageHint)) { + return mVibrator.getDefaultHapticFeedbackIntensity(); + } + } + } + return Vibrator.VIBRATION_INTENSITY_MEDIUM; } /** @@ -234,8 +251,11 @@ final class VibrationSettings { if (!isRingtone(usageHint)) { return true; } - int ringerMode = mAudioManager.getRingerModeInternal(); synchronized (mLock) { + if (mAudioManager == null) { + return false; + } + int ringerMode = mAudioManager.getRingerModeInternal(); if (mVibrateWhenRinging) { return ringerMode != AudioManager.RINGER_MODE_SILENT; } else if (mApplyRampingRinger) { @@ -304,12 +324,12 @@ final class VibrationSettings { mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0; mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0; mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - mVibrator.getDefaultHapticFeedbackIntensity()); + getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); mNotificationIntensity = getSystemSetting( Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - mVibrator.getDefaultNotificationVibrationIntensity()); + getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, - mVibrator.getDefaultRingVibrationIntensity()); + getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); } @@ -346,15 +366,15 @@ final class VibrationSettings { proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, mHapticFeedbackIntensity); proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, - mVibrator.getDefaultHapticFeedbackIntensity()); + getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY, mNotificationIntensity); proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, - mVibrator.getDefaultNotificationVibrationIntensity()); + getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY, mRingIntensity); proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY, - mVibrator.getDefaultRingVibrationIntensity()); + getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); } } diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 95f6059c482e..eaba083c551c 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -87,8 +87,8 @@ final class VibratorController { static native long vibratorPerformEffect( long nativePtr, long effect, long strength, long vibrationId); - static native void vibratorPerformComposedEffect(long nativePtr, - VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId); + static native long vibratorPerformComposedEffect( + long nativePtr, VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId); static native void vibratorSetExternalControl(long nativePtr, boolean enabled); @@ -269,13 +269,9 @@ final class VibratorController { VibrationEffect.Composition.PrimitiveEffect[] primitives = effect.getPrimitiveEffects().toArray( new VibrationEffect.Composition.PrimitiveEffect[0]); - mNativeWrapper.compose(primitives, vibrationId); - notifyVibratorOnLocked(); - // Compose don't actually give us an estimated duration, so we just guess here. - long duration = 0; - for (VibrationEffect.Composition.PrimitiveEffect primitive : primitives) { - // TODO(b/177807015): use exposed durations from IVibrator here instead - duration += 20 + primitive.delay; + long duration = mNativeWrapper.compose(primitives, vibrationId); + if (duration > 0) { + notifyVibratorOnLocked(); } return duration; } @@ -393,9 +389,9 @@ final class VibratorController { } /** Turns vibrator on to perform one of the supported composed effects. */ - public void compose( + public long compose( VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) { - VibratorController.vibratorPerformComposedEffect(mNativePtr, effect, + return VibratorController.vibratorPerformComposedEffect(mNativePtr, effect, vibrationId); } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 175085475b6c..c9751bb7abe4 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -127,9 +127,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") private ExternalVibrationHolder mCurrentExternalVibration; - private VibrationSettings mVibrationSettings; - private VibrationScaler mVibrationScaler; - private InputDeviceDelegate mInputDeviceDelegate; + private final VibrationSettings mVibrationSettings; + private final VibrationScaler mVibrationScaler; + private final InputDeviceDelegate mInputDeviceDelegate; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -170,6 +170,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mContext = context; mHandler = injector.createHandler(Looper.myLooper()); + mVibrationSettings = new VibrationSettings(mContext, mHandler); + mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); + mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler); + VibrationCompleteListener listener = new VibrationCompleteListener(this); mNativeWrapper = injector.getNativeWrapper(); mNativeWrapper.init(listener); @@ -224,12 +228,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.v(TAG, "Initializing VibratorManager service..."); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "systemReady"); try { - mVibrationSettings = new VibrationSettings(mContext, mHandler); - mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); - mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler); + mVibrationSettings.onSystemReady(); + mInputDeviceDelegate.onSystemReady(); mVibrationSettings.addListener(this::updateServiceState); + // Will update settings and input devices. updateServiceState(); } finally { Slog.v(TAG, "VibratorManager service initialized"); @@ -340,10 +344,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (!isEffectValid(effect)) { return; } - effect = fixupVibrationEffect(effect); attrs = fixupVibrationAttributes(attrs); Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs, uid, opPkg, reason); + // Update with fixed up effect to keep the original effect in Vibration for debugging. + vib.updateEffect(fixupVibrationEffect(effect)); synchronized (mLock) { Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib); @@ -1134,6 +1139,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { void dumpText(PrintWriter pw) { pw.println("Vibrator Manager Service:"); synchronized (mLock) { + pw.println(" mVibrationSettings:"); + pw.println(" " + mVibrationSettings); + pw.println(); pw.println(" mVibratorControllers:"); for (int i = 0; i < mVibrators.size(); i++) { pw.println(" " + mVibrators.valueAt(i)); @@ -1142,14 +1150,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" mCurrentVibration:"); pw.println(" " + (mCurrentVibration == null ? null : mCurrentVibration.getVibration().getDebugInfo())); + pw.println(); pw.println(" mNextVibration:"); pw.println(" " + (mNextVibration == null ? null : mNextVibration.getVibration().getDebugInfo())); + pw.println(); pw.println(" mCurrentExternalVibration:"); pw.println(" " + (mCurrentExternalVibration == null ? null : mCurrentExternalVibration.getDebugInfo())); pw.println(); - pw.println(" mVibrationSettings=" + mVibrationSettings); for (int i = 0; i < mPreviousVibrations.size(); i++) { pw.println(); pw.print(" Previous vibrations for usage "); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 3bbc81a696e6..e6d37b60882e 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1861,7 +1861,7 @@ final class AccessibilityController { } @Override - public boolean isEnabled() { + public boolean isAccessibilityTracingEnabled() { return mTracing.isEnabled(); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c1fe4887da57..298128a6a222 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -132,7 +132,6 @@ import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE; import static com.android.server.wm.ActivityRecordProto.DEFER_HIDING_CLIENT; import static com.android.server.wm.ActivityRecordProto.FILLS_PARENT; import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK; -import static com.android.server.wm.ActivityRecordProto.FROZEN_BOUNDS; import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING; import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START; import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN; @@ -212,9 +211,10 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; +import static com.android.server.wm.WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; +import static com.android.server.wm.WindowManagerService.letterboxBackgroundTypeToString; import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; @@ -344,7 +344,6 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -582,9 +581,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private Task mLastParent; - // Have we told the window clients to show themselves? - private boolean mClientVisible; - boolean firstWindowDrawn; /** Whether the visible window(s) of this activity is drawn. */ private boolean mReportedDrawn; @@ -732,9 +728,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // windows, where the app hasn't had time to set a value on the window. int mRotationAnimationHint = -1; - ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); - ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); - private AppSaturationInfo mLastAppSaturationInfo; private final ColorDisplayService.ColorTransformController mColorTransformController = @@ -763,6 +756,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Token for targeting this activity for assist purposes. final Binder assistToken = new Binder(); + // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be used + // without security checks + final Binder shareableActivityToken = new Binder(); + // Tracking cookie for the launch of this activity and it's task. IBinder mLaunchCookie; @@ -911,7 +908,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(Integer.toHexString(taskDescription.getStatusBarColor())); pw.print(" navigationBarColor="); pw.println(Integer.toHexString(taskDescription.getNavigationBarColor())); - pw.print(" backgroundColorFloating="); + pw.print(prefix); pw.print(" backgroundColorFloating="); pw.println(Integer.toHexString( taskDescription.getBackgroundColorFloating())); } @@ -999,7 +996,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(prefix); pw.print("mOrientation="); pw.println(ActivityInfo.screenOrientationToString(mOrientation)); pw.println(prefix + "mVisibleRequested=" + mVisibleRequested - + " mVisible=" + mVisible + " mClientVisible=" + mClientVisible + + " mVisible=" + mVisible + " mClientVisible=" + isClientVisible() + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "") + " reportedDrawn=" + mReportedDrawn + " reportedVisible=" + reportedVisible); if (paused) { @@ -1031,10 +1028,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(" mVisibleSetFromTransferredStartingWindow=" + mVisibleSetFromTransferredStartingWindow); } - if (!mFrozenBounds.isEmpty()) { - pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds); - pw.print(prefix); pw.print("mFrozenMergedConfig="); pw.println(mFrozenMergedConfig); - } if (mPendingRelaunchCount != 0) { pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount); } @@ -1085,6 +1078,46 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges)); } } + + dumpLetterboxInfo(pw, prefix); + } + + private void dumpLetterboxInfo(PrintWriter pw, String prefix) { + final WindowState mainWin = findMainWindow(); + if (mainWin == null) { + return; + } + + boolean isLetterboxed = isLetterboxed(mainWin); + pw.println(prefix + "isLetterboxed=" + isLetterboxed); + if (!isLetterboxed) { + return; + } + + pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin)); + pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( + getLetterboxBackgroundColor().toArgb())); + pw.println(prefix + " letterboxBackgroundType=" + + letterboxBackgroundTypeToString(mWmService.getLetterboxBackgroundType())); + pw.println(prefix + " letterboxAspectRatio=" + + computeAspectRatio(getBounds())); + } + + /** + * Returns a string representing the reason for letterboxing. This method assumes the activity + * is letterboxed. + */ + private String getLetterboxReasonString(WindowState mainWin) { + if (inSizeCompatMode()) { + return "SIZE_COMPAT_MODE"; + } + if (isLetterboxedForFixedOrientationAndAspectRatio()) { + return "FIXED_ORIENTATION"; + } + if (mainWin.isLetterboxedForDisplayCutout()) { + return "DISPLAY_CUTOUT"; + } + return "UNKNOWN_REASON"; } void setAppTimeTracker(AppTimeTracker att) { @@ -1700,7 +1733,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A keysPaused = false; inHistory = false; nowVisible = false; - mClientVisible = true; + super.setClientVisible(true); idle = false; hasBeenLaunched = false; mTaskSupervisor = supervisor; @@ -3355,56 +3388,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mPendingRelaunchCount > 0; } - boolean shouldFreezeBounds() { - // For freeform windows, we can't freeze the bounds at the moment because this would make - // the resizing unresponsive. - if (task == null || task.inFreeformWindowingMode()) { - return false; - } - - // We freeze the bounds while drag resizing to deal with the time between - // the divider/drag handle being released, and the handling it's new - // configuration. If we are relaunched outside of the drag resizing state, - // we need to be careful not to do this. - return task.isDragResizing(); - } - @VisibleForTesting void startRelaunching() { if (mPendingRelaunchCount == 0) { mRelaunchStartTime = SystemClock.elapsedRealtime(); } - if (shouldFreezeBounds()) { - freezeBounds(); - } - clearAllDrawn(); mPendingRelaunchCount++; } - /** - * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds - * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even - * if they change in the meantime. If the bounds are already frozen, the bounds will be frozen - * with a queue. - */ - private void freezeBounds() { - mFrozenBounds.offer(new Rect(task.mPreparedFrozenBounds)); - - if (task.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) { - // We didn't call prepareFreezingBounds on the task, so use the current value. - mFrozenMergedConfig.offer(new Configuration(task.getConfiguration())); - } else { - mFrozenMergedConfig.offer(new Configuration(task.mPreparedFrozenMergedConfig)); - } - // Calling unset() to make it equal to Configuration.EMPTY. - task.mPreparedFrozenMergedConfig.unset(); - } - void finishRelaunching() { mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this); - unfreezeBounds(); if (mPendingRelaunchCount > 0) { mPendingRelaunchCount--; @@ -3426,30 +3421,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mPendingRelaunchCount == 0) { return; } - unfreezeBounds(); mPendingRelaunchCount = 0; mRelaunchStartTime = 0; } /** - * Unfreezes the previously frozen bounds. See {@link #freezeBounds}. - */ - private void unfreezeBounds() { - if (mFrozenBounds.isEmpty()) { - return; - } - mFrozenBounds.remove(); - if (!mFrozenMergedConfig.isEmpty()) { - mFrozenMergedConfig.remove(); - } - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState win = mChildren.get(i); - win.onUnfreezeBounds(); - } - mWmService.mWindowPlacerLocked.performSurfacePlacement(); - } - - /** * Perform clean-up of service connections in an activity record. */ private void cleanUpActivityServices() { @@ -3797,7 +3773,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A setVisibleRequested(true); mVisibleSetFromTransferredStartingWindow = true; } - setClientVisible(fromActivity.mClientVisible); + setClientVisible(fromActivity.isClientVisible()); if (fromActivity.isAnimating()) { transferAnimation(fromActivity); @@ -4445,6 +4421,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mVisibleRequested = visible; + setInsetsFrozen(!visible); if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); } @@ -5897,18 +5874,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mReportedDrawn; } - boolean isClientVisible() { - return mClientVisible; - } - + @Override void setClientVisible(boolean clientVisible) { - if (mClientVisible == clientVisible || (!clientVisible && mDeferHidingClient)) { - return; - } + // TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions. + // pip activities should just remain in clientVisible. + if (!clientVisible && mDeferHidingClient) return; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible, Debug.getCallers(5)); - mClientVisible = clientVisible; + super.setClientVisible(clientVisible); sendAppVisibilityToClients(); } @@ -7020,6 +6994,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A float aspect = Math.max(parentWidth, parentHeight) / (float) Math.min(parentWidth, parentHeight); + // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with + // set-fixed-orientation-letterbox-aspect-ratio. + final float letterboxAspectRatioOverride = + mWmService.getFixedOrientationLetterboxAspectRatio(); + aspect = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO + ? letterboxAspectRatioOverride : aspect; + // 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; @@ -7030,16 +7011,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A aspect = minAspectRatio; } - // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio. - // TODO(b/175212232): Rename getTaskLetterboxAspectRatio and all related methods since fixed - // orientation letterbox is on the activity level now. - final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio(); - // Activity min/max aspect ratio restrictions will be respected by the activity-level - // letterboxing (size-compat mode). Therefore this override can control the maximum screen - // area that can be occupied by the app in the letterbox mode. - aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO - ? letterboxAspectRatioOverride : aspect; - // Store the current bounds to be able to revert to size compat mode values below if needed. Rect mTmpFullBounds = new Rect(resolvedBounds); if (forcedOrientation == ORIENTATION_LANDSCAPE) { @@ -7452,8 +7423,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int containingAppWidth = containingAppBounds.width(); final int containingAppHeight = containingAppBounds.height(); - final float containingRatio = Math.max(containingAppWidth, containingAppHeight) - / (float) Math.min(containingAppWidth, containingAppHeight); + final float containingRatio = computeAspectRatio(containingAppBounds); int activityWidth = containingAppWidth; int activityHeight = containingAppHeight; @@ -7517,6 +7487,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * Returns the aspect ratio of the given {@code rect}. + */ + private static float computeAspectRatio(Rect rect) { + final int width = rect.width(); + final int height = rect.height(); + if (width == 0 || height == 0) { + return 0; + } + return Math.max(width, height) / (float) Math.min(width, height); + } + + /** * @return {@code true} if this activity was reparented to another display but * {@link #ensureActivityConfiguration} is not called. */ @@ -8195,7 +8177,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A proto.write(TRANSLUCENT, !occludesParent()); proto.write(VISIBLE, mVisible); proto.write(VISIBLE_REQUESTED, mVisibleRequested); - proto.write(CLIENT_VISIBLE, mClientVisible); + proto.write(CLIENT_VISIBLE, isClientVisible()); proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient); proto.write(REPORTED_DRAWN, mReportedDrawn); proto.write(REPORTED_VISIBLE, reportedVisible); @@ -8210,9 +8192,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A proto.write(STARTING_MOVED, startingMoved); proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW, mVisibleSetFromTransferredStartingWindow); - for (Rect bounds : mFrozenBounds) { - bounds.dumpDebug(proto, FROZEN_BOUNDS); - } proto.write(STATE, mState.toString()); proto.write(FRONT_OF_TASK, isRootOfTask()); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 94379b1f230e..e858fe1034b1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -348,13 +348,16 @@ public abstract class ActivityTaskManagerInternal { public final class ActivityTokens { private final @NonNull IBinder mActivityToken; private final @NonNull IBinder mAssistToken; + private final @NonNull IBinder mShareableActivityToken; private final @NonNull IApplicationThread mAppThread; public ActivityTokens(@NonNull IBinder activityToken, - @NonNull IBinder assistToken, @NonNull IApplicationThread appThread) { + @NonNull IBinder assistToken, @NonNull IApplicationThread appThread, + @NonNull IBinder shareableActivityToken) { mActivityToken = activityToken; mAssistToken = assistToken; mAppThread = appThread; + mShareableActivityToken = shareableActivityToken; } /** @@ -372,6 +375,13 @@ public abstract class ActivityTaskManagerInternal { } /** + * @return The sharable activity token.. + */ + public @NonNull IBinder getShareableActivityToken() { + return mShareableActivityToken; + } + + /** * @return The assist token. */ public @NonNull IApplicationThread getApplicationThread() { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index db499158a5d9..0c77d9f1f724 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4042,17 +4042,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale, boolean persistent, int userId) { - final DisplayContent defaultDisplay = - mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY); - mTempConfig.setTo(getGlobalConfiguration()); final int changes = mTempConfig.updateFrom(values); if (changes == 0) { - // Since calling to Activity.setRequestedOrientation leads to freezing the window with - // setting WindowManagerService.mWaitingForConfig to true, it is important that we call - // performDisplayOverrideConfigUpdate in order to send the new display configuration - // (even if there are no actual changes) to unfreeze the window. - defaultDisplay.performDisplayOverrideConfigUpdate(values); return 0; } @@ -5550,7 +5542,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return null; } return new ActivityTokens(activity.appToken, activity.assistToken, - activity.app.getThread()); + activity.app.getThread(), activity.shareableActivityToken); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index bf2aae8867ba..db751e9759fa 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -825,7 +825,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { r.getSavedState(), r.getPersistentSavedState(), results, newIntents, r.takeOptions(), dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController, - r.createFixedRotationAdjustmentsIfNeeded())); + r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken)); // Set desired final state. final ActivityLifecycleItem lifecycleItem; @@ -1504,7 +1504,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Prevent recursion. return; } - mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CLOSE, task); + if (task.isVisible()) { + mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CLOSE, task); + } else { + // Removing a non-visible task doesn't require a transition, but if there is one + // collecting, this should be a member just in case. + mService.getTransitionController().collect(task); + } task.mInRemoveTask = true; try { task.performClearTask(reason); diff --git a/services/core/java/com/android/server/wm/BlurController.java b/services/core/java/com/android/server/wm/BlurController.java new file mode 100644 index 000000000000..13295e8aca02 --- /dev/null +++ b/services/core/java/com/android/server/wm/BlurController.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 com.android.server.wm; + +import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; + +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.view.ICrossWindowBlurEnabledListener; + +final class BlurController { + + private final RemoteCallbackList<ICrossWindowBlurEnabledListener> + mBlurEnabledListeners = new RemoteCallbackList<>(); + private final Object mLock = new Object(); + boolean mBlurEnabled; + + BlurController() { + mBlurEnabled = CROSS_WINDOW_BLUR_SUPPORTED; + } + + boolean registerCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) { + if (listener == null) return false; + mBlurEnabledListeners.register(listener); + synchronized (mLock) { + return mBlurEnabled; + } + } + + void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) { + if (listener == null) return; + mBlurEnabledListeners.unregister(listener); + } + + private void updateBlurEnabled() { + // TODO: add other factors disabling blurs + final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED; + synchronized (mLock) { + if (mBlurEnabled == newEnabled) { + return; + } + mBlurEnabled = newEnabled; + notifyBlurEnabledChanged(newEnabled); + } + } + + private void notifyBlurEnabledChanged(boolean enabled) { + int i = mBlurEnabledListeners.beginBroadcast(); + while (i > 0) { + i--; + ICrossWindowBlurEnabledListener listener = + mBlurEnabledListeners.getBroadcastItem(i); + try { + listener.onCrossWindowBlurEnabledChanged(enabled); + } catch (RemoteException e) { + } + } + mBlurEnabledListeners.finishBroadcast(); + } + + +} diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 309b5ec25f0f..62a00802896f 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -141,17 +141,20 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { mChangeListeners.get(i).onMergedOverrideConfigurationChanged( mMergedOverrideConfiguration); } - dispatchConfigurationToChildren(); - } - - void dispatchConfigurationToChildren() { for (int i = getChildCount() - 1; i >= 0; --i) { - final ConfigurationContainer child = getChildAt(i); - child.onConfigurationChanged(mFullConfiguration); + dispatchConfigurationToChild(getChildAt(i), mFullConfiguration); } } /** + * Dispatches the configuration to child when {@link #onConfigurationChanged(Configuration)} is + * called. This allows the derived classes to override how to dispatch the configuration. + */ + void dispatchConfigurationToChild(E child, Configuration config) { + child.onConfigurationChanged(config); + } + + /** * Resolves the current requested override configuration into * {@link #mResolvedOverrideConfiguration} * diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 759b7fe054bc..5ccf576e1099 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -495,6 +495,21 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { return info; } + /** + * Gets the stable bounds of the DisplayArea, which is the bounds excluding insets for + * navigation bar, cutout, and status bar. + */ + void getStableRect(Rect out) { + if (mDisplayContent == null) { + getBounds(out); + return; + } + + // Intersect with the display stable bounds to get the DisplayArea stable bounds. + mDisplayContent.getStableRect(out); + out.intersect(getBounds()); + } + @Override public boolean providesMaxBounds() { return true; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index eff4ea6536bd..119ffb732283 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2677,6 +2677,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mDisplayWindowSettings.setForcedSize(this, width, height); } + @Override void getStableRect(Rect out) { final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); out.set(state.getDisplayFrame()); @@ -2943,10 +2944,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return dockFrame.bottom - imeFrame.top; } - void prepareFreezingTaskBounds() { - forAllRootTasks(Task::prepareFreezingTaskBounds); - } - void rotateBounds(@Rotation int oldRotation, @Rotation int newRotation, Rect inOutBounds) { // Get display bounds on oldRotation as parent bounds for the rotation. getBounds(mTmpRect, oldRotation); @@ -3703,8 +3700,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // app. assignWindowLayers(true /* setLayoutNeeded */); // 3. The z-order of IME might have been changed. Update the above insets state. - mInsetsStateController.updateAboveInsetsState( - mInputMethodWindow, true /* notifyInsetsChange */); + mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, + mInsetsStateController.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); // 4. Update the IME control target to apply any inset change and animation. // 5. Reparent the IME container surface to either the input target app, or the IME window // parent. @@ -4105,13 +4102,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) { if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) { - // Unfreeze the insets state of the frozen target when the animation finished if exists. - final Task task = wc.asTask(); - if (task != null) { - task.forAllWindows(w -> { - w.clearFrozenInsetsState(); - }, true /* traverseTopToBottom */); - } removeImeSurfaceImmediately(); } } @@ -4833,7 +4823,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mNoAnimationNotifyOnTransitionFinished.clear(); - mWallpaperController.hideDeferredWallpapersIfNeeded(); + mWallpaperController.hideDeferredWallpapersIfNeededLegacy(); onAppTransitionDone(); @@ -5272,13 +5262,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp || windowingMode == WINDOWING_MODE_MULTI_WINDOW); } - static boolean canReuseExistingTask(int windowingMode, int activityType) { - // Existing Tasks can be reused if a new root task will be created anyway, or for the - // Dream - because there can only ever be one DreamActivity. - return alwaysCreateRootTask(windowingMode, activityType) - || activityType == ACTIVITY_TYPE_DREAM; - } - @Nullable Task getFocusedRootTask() { return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedRootTask); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 32152ec85493..af9cdeb52fe3 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -69,6 +69,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; +import static android.view.WindowManager.LayoutParams.TYPE_POINTER; import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; @@ -242,7 +243,7 @@ public class DisplayPolicy { } } - private final SystemGesturesPointerEventListener mSystemGestures; + private SystemGesturesPointerEventListener mSystemGestures; private volatile int mLidState = LID_ABSENT; private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED; @@ -384,7 +385,7 @@ public class DisplayPolicy { private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; - private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; + private GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; private final WindowManagerInternal.AppTransitionListener mAppTransitionListener; @@ -448,119 +449,6 @@ public class DisplayPolicy { final Looper looper = UiThread.getHandler().getLooper(); mHandler = new PolicyHandler(looper); - mSystemGestures = new SystemGesturesPointerEventListener(mContext, mHandler, - new SystemGesturesPointerEventListener.Callbacks() { - @Override - public void onSwipeFromTop() { - synchronized (mLock) { - if (mStatusBar != null) { - requestTransientBars(mStatusBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_TOP); - } - } - - @Override - public void onSwipeFromBottom() { - synchronized (mLock) { - if (mNavigationBar != null - && mNavigationBarPosition == NAV_BAR_BOTTOM) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM); - } - } - - @Override - public void onSwipeFromRight() { - final Region excludedRegion = Region.obtain(); - synchronized (mLock) { - mDisplayContent.calculateSystemGestureExclusion( - excludedRegion, null /* outUnrestricted */); - final boolean excluded = - mSystemGestures.currentGestureStartedInRegion(excludedRegion); - if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_RIGHT - || !excluded && mNavigationBarAlwaysShowOnSideGesture)) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT); - } - excludedRegion.recycle(); - } - - @Override - public void onSwipeFromLeft() { - final Region excludedRegion = Region.obtain(); - synchronized (mLock) { - mDisplayContent.calculateSystemGestureExclusion( - excludedRegion, null /* outUnrestricted */); - final boolean excluded = - mSystemGestures.currentGestureStartedInRegion(excludedRegion); - if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_LEFT - || !excluded && mNavigationBarAlwaysShowOnSideGesture)) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_LEFT); - } - excludedRegion.recycle(); - } - - @Override - public void onFling(int duration) { - if (mService.mPowerManagerInternal != null) { - mService.mPowerManagerInternal.setPowerBoost( - Boost.INTERACTION, duration); - } - } - - @Override - public void onDebug() { - // no-op - } - - private WindowOrientationListener getOrientationListener() { - final DisplayRotation rotation = mDisplayContent.getDisplayRotation(); - return rotation != null ? rotation.getOrientationListener() : null; - } - - @Override - public void onDown() { - final WindowOrientationListener listener = getOrientationListener(); - if (listener != null) { - listener.onTouchStart(); - } - } - - @Override - public void onUpOrCancel() { - final WindowOrientationListener listener = getOrientationListener(); - if (listener != null) { - listener.onTouchEnd(); - } - } - - @Override - public void onMouseHoverAtTop() { - mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS); - Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS); - msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS; - mHandler.sendMessageDelayed(msg, 500 /* delayMillis */); - } - - @Override - public void onMouseHoverAtBottom() { - mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS); - Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS); - msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION; - mHandler.sendMessageDelayed(msg, 500 /* delayMillis */); - } - - @Override - public void onMouseLeaveFromEdge() { - mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS); - } - }); - displayContent.registerPointerEventListener(mSystemGestures); mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() { private Runnable mAppTransitionPending = () -> { @@ -616,7 +504,7 @@ public class DisplayPolicy { mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper, mService.mVrModeEnabled); - // TODO: Make it can take screenshot on external display + // TODO(b/180986447): Make it can take screenshot on external display mScreenshotHelper = displayContent.isDefaultDisplay ? new ScreenshotHelper(mContext) : null; @@ -640,16 +528,6 @@ public class DisplayPolicy { mRefreshRatePolicy = new RefreshRatePolicy(mService, mDisplayContent.getDisplayInfo(), mService.mHighRefreshRateDenylist); - - mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(mHandler, - mContext, () -> { - synchronized (mLock) { - onConfigurationChanged(); - mSystemGestures.onConfigurationChanged(); - mDisplayContent.updateSystemGestureExclusion(); - } - }); - mHandler.post(mGestureNavigationSettingsObserver::register); } private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos) { @@ -668,12 +546,154 @@ public class DisplayPolicy { } void systemReady() { - mSystemGestures.systemReady(); if (mService.mPointerLocationEnabled) { setPointerLocationEnabled(true); } } + @NonNull + private GestureNavigationSettingsObserver getGestureNavigationSettingsObserver() { + if (mGestureNavigationSettingsObserver == null) { + mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(mHandler, + mContext, () -> { + synchronized (mLock) { + onConfigurationChanged(); + getSystemGestures().onConfigurationChanged(); + mDisplayContent.updateSystemGestureExclusion(); + } + }); + mHandler.post(mGestureNavigationSettingsObserver::register); + } + return mGestureNavigationSettingsObserver; + } + + @NonNull + private SystemGesturesPointerEventListener getSystemGestures() { + if (mSystemGestures == null) { + final Context gestureContext = mUiContext.createWindowContext( + mDisplayContent.getDisplay(), TYPE_POINTER, null /* options */); + mSystemGestures = new SystemGesturesPointerEventListener(gestureContext, mHandler, + new SystemGesturesPointerEventListener.Callbacks() { + @Override + public void onSwipeFromTop() { + synchronized (mLock) { + if (mStatusBar != null) { + requestTransientBars(mStatusBar); + } + checkAltBarSwipeForTransientBars(ALT_BAR_TOP); + } + } + + @Override + public void onSwipeFromBottom() { + synchronized (mLock) { + if (mNavigationBar != null + && mNavigationBarPosition == NAV_BAR_BOTTOM) { + requestTransientBars(mNavigationBar); + } + checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM); + } + } + + @Override + public void onSwipeFromRight() { + final Region excludedRegion = Region.obtain(); + synchronized (mLock) { + mDisplayContent.calculateSystemGestureExclusion( + excludedRegion, null /* outUnrestricted */); + final boolean excluded = mSystemGestures + .currentGestureStartedInRegion(excludedRegion); + if (mNavigationBar != null + && (mNavigationBarPosition == NAV_BAR_RIGHT + || !excluded && mNavigationBarAlwaysShowOnSideGesture)) { + requestTransientBars(mNavigationBar); + } + checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT); + } + excludedRegion.recycle(); + } + + @Override + public void onSwipeFromLeft() { + final Region excludedRegion = Region.obtain(); + synchronized (mLock) { + mDisplayContent.calculateSystemGestureExclusion( + excludedRegion, null /* outUnrestricted */); + final boolean excluded = mSystemGestures + .currentGestureStartedInRegion(excludedRegion); + if (mNavigationBar != null + && (mNavigationBarPosition == NAV_BAR_LEFT + || !excluded && mNavigationBarAlwaysShowOnSideGesture)) { + requestTransientBars(mNavigationBar); + } + checkAltBarSwipeForTransientBars(ALT_BAR_LEFT); + } + excludedRegion.recycle(); + } + + @Override + public void onFling(int duration) { + if (mService.mPowerManagerInternal != null) { + mService.mPowerManagerInternal.setPowerBoost( + Boost.INTERACTION, duration); + } + } + + @Override + public void onDebug() { + // no-op + } + + private WindowOrientationListener getOrientationListener() { + final DisplayRotation rotation = mDisplayContent.getDisplayRotation(); + return rotation != null ? rotation.getOrientationListener() : null; + } + + @Override + public void onDown() { + final WindowOrientationListener listener = getOrientationListener(); + if (listener != null) { + listener.onTouchStart(); + } + } + + @Override + public void onUpOrCancel() { + final WindowOrientationListener listener = getOrientationListener(); + if (listener != null) { + listener.onTouchEnd(); + } + } + + @Override + public void onMouseHoverAtTop() { + mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS); + Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS); + msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS; + mHandler.sendMessageDelayed(msg, 500 /* delayMillis */); + } + + @Override + public void onMouseHoverAtBottom() { + mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS); + Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS); + msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION; + mHandler.sendMessageDelayed(msg, 500 /* delayMillis */); + } + + @Override + public void onMouseLeaveFromEdge() { + mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS); + } + }); + mDisplayContent.registerPointerEventListener(getSystemGestures()); + if (mService.mSystemReady) { + mSystemGestures.systemReady(); + } + } + return mSystemGestures; + } + private int getDisplayId() { return mDisplayContent.getDisplayId(); } @@ -1455,8 +1475,7 @@ public class DisplayPolicy { } void onDisplayInfoChanged(DisplayInfo info) { - mSystemGestures.screenWidth = info.logicalWidth; - mSystemGestures.screenHeight = info.logicalHeight; + getSystemGestures().onDisplayInfoChanged(info); } private void layoutStatusBar(DisplayFrames displayFrames, Rect contentFrame) { @@ -1969,7 +1988,7 @@ public class DisplayPolicy { public void onOverlayChangedLw() { updateCurrentUserResources(); onConfigurationChanged(); - mSystemGestures.onConfigurationChanged(); + getSystemGestures().onConfigurationChanged(); } /** @@ -2040,10 +2059,10 @@ public class DisplayPolicy { } mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode); - mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res); - mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res); - mNavButtonForcedVisible = - mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); + final GestureNavigationSettingsObserver observer = getGestureNavigationSettingsObserver(); + mLeftGestureInset = observer.getLeftSensitivity(res); + mRightGestureInset = observer.getRightSensitivity(res); + mNavButtonForcedVisible = observer.areNavigationButtonForcedVisible(); mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough); mNavigationBarAlwaysShowOnSideGesture = res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture); @@ -3056,7 +3075,7 @@ public class DisplayPolicy { } void release() { - mHandler.post(mGestureNavigationSettingsObserver::unregister); + mHandler.post(getGestureNavigationSettingsObserver()::unregister); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 1f7e1524b702..316c20ba5c47 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -99,6 +99,9 @@ class EnsureActivitiesVisibleHelper { mTask.forAllActivities(a -> { setActivityVisibilityState(a, starting, resumeTopActivity); }); + if (mTask.mAtmService.getTransitionController().getTransitionPlayer() != null) { + mTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows(); + } } private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting, diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 27ef147e2781..28c5a6d9323d 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -410,15 +410,19 @@ final class InputMonitor { return; } + requestFocus(focusToken, focus.getName()); + } + + private void requestFocus(IBinder focusToken, String windowName) { if (focusToken == mInputFocus) { return; } mInputFocus = focusToken; - mInputTransaction.setFocusedWindow(mInputFocus, focus.getName(), mDisplayId); - EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + focus, + mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId); + EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName, "reason=UpdateInputWindows"); - ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus); + ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName); } void setFocusedAppLw(ActivityRecord newApp) { @@ -470,6 +474,8 @@ final class InputMonitor { boolean mInDrag; + private boolean mRecentsAnimationFocusOverride; + private void updateInputWindows(boolean inDrag) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows"); @@ -485,10 +491,16 @@ final class InputMonitor { mInDrag = inDrag; resetInputConsumers(mInputTransaction); - + mRecentsAnimationFocusOverride = false; mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */); - updateInputFocusRequest(); + if (mRecentsAnimationFocusOverride) { + requestFocus(mRecentsAnimationInputConsumer.mWindowHandle.token, + mRecentsAnimationInputConsumer.mName); + } else { + updateInputFocusRequest(); + } + if (!mUpdateInputWindowsImmediately) { mDisplayContent.getPendingTransaction().merge(mInputTransaction); @@ -526,6 +538,7 @@ final class InputMonitor { mRecentsAnimationInputConsumer.mWindowHandle)) { mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord); mAddRecentsAnimationInputConsumerHandle = false; + mRecentsAnimationFocusOverride = true; } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 75176df6aaf7..a971794dc97d 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -124,7 +124,8 @@ class InsetsStateController { ? provider.getSource().getType() : ITYPE_INVALID; return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(), target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : - target.mAboveInsetsState); + (target.mAttrs.receiveInsetsIgnoringZOrder ? mState : + target.mAboveInsetsState)); } InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index e18516d7bc3a..62c155a3c198 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -540,7 +540,7 @@ public class LockTaskController { setStatusBarState(mLockTaskModeState, userId); setKeyguardState(mLockTaskModeState, userId); if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) { - lockKeyguardIfNeeded(); + lockKeyguardIfNeeded(userId); } if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId); @@ -886,15 +886,15 @@ public class LockTaskController { * Helper method for locking the device immediately. This may be necessary when the device * leaves the pinned mode. */ - private void lockKeyguardIfNeeded() { - if (shouldLockKeyguard()) { + private void lockKeyguardIfNeeded(int userId) { + if (shouldLockKeyguard(userId)) { mWindowManager.lockNow(null); mWindowManager.dismissKeyguard(null /* callback */, null /* message */); getLockPatternUtils().requireCredentialEntry(USER_ALL); } } - private boolean shouldLockKeyguard() { + private boolean shouldLockKeyguard(int userId) { // This functionality should be kept consistent with // com.android.settings.security.ScreenPinningSettings (see b/127605586) try { @@ -904,7 +904,7 @@ public class LockTaskController { } catch (Settings.SettingNotFoundException e) { // Log to SafetyNet for b/127605586 android.util.EventLog.writeEvent(0x534e4554, "127605586", -1, ""); - return getLockPatternUtils().isSecure(USER_CURRENT); + return getLockPatternUtils().isSecure(userId); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 3f9ea1fd2afd..0e8cadbcbcdd 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -650,28 +650,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } @Override - void dispatchConfigurationToChildren() { - final Configuration configuration = getConfiguration(); - for (int i = getChildCount() - 1; i >= 0; i--) { - final DisplayContent displayContent = getChildAt(i); - if (displayContent.isDefaultDisplay) { - // The global configuration is also the override configuration of default display. - displayContent.performDisplayOverrideConfigUpdate(configuration); - } else { - displayContent.onConfigurationChanged(configuration); - } - } - } - - @Override - public void onConfigurationChanged(Configuration newParentConfig) { - prepareFreezingTaskBounds(); - super.onConfigurationChanged(newParentConfig); - } - - private void prepareFreezingTaskBounds() { - for (int i = mChildren.size() - 1; i >= 0; i--) { - mChildren.get(i).prepareFreezingTaskBounds(); + void dispatchConfigurationToChild(DisplayContent child, Configuration config) { + if (child.isDefaultDisplay) { + // The global configuration is also the override configuration of default display. + child.performDisplayOverrideConfigUpdate(config); + } else { + child.onConfigurationChanged(config); } } diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java index f3859b41b6fd..a98a47802914 100644 --- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java +++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java @@ -126,11 +126,17 @@ class SystemGesturesPointerEventListener implements PointerEventListener { Slog.w(TAG, "Cannot create GestureDetector, display removed:" + displayId); return; } + onDisplayInfoChanged(info); mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler) { }; }); } + void onDisplayInfoChanged(DisplayInfo info) { + screenWidth = info.logicalWidth; + screenHeight = info.logicalHeight; + } + @Override public void onPointerEvent(MotionEvent event) { if (mGestureDetector != null && event.isTouchEvent()) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8aa00d0d896f..ea9f2c0e2ae3 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -21,6 +21,7 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -498,9 +499,6 @@ class Task extends WindowContainer<WindowContainer> { // TODO: Make final int mUserId; - final Rect mPreparedFrozenBounds = new Rect(); - final Configuration mPreparedFrozenMergedConfig = new Configuration(); - // Id of the previous display the root task was on. int mPrevDisplayId = INVALID_DISPLAY; @@ -1182,10 +1180,6 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.mNoAnimActivities.add(topActivity); } - // We might trigger a configuration change. Save the current task bounds for freezing. - // TODO: Should this call be moved inside the resize method in WM? - toRootTask.prepareFreezingTaskBounds(); - if (toRootTaskWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) { // Move recents to front so it is not behind root home task when going into docked @@ -1260,10 +1254,13 @@ class Task extends WindowContainer<WindowContainer> { * @param info The activity info which could be different from {@code r.info} if set. */ void setIntent(ActivityRecord r, @Nullable Intent intent, @Nullable ActivityInfo info) { - mCallingUid = r.launchedFromUid; - mCallingPackage = r.launchedFromPackage; - mCallingFeatureId = r.launchedFromFeatureId; - setIntent(intent != null ? intent : r.intent, info != null ? info : r.info); + if (this.intent == null || !mNeverRelinquishIdentity) { + mCallingUid = r.launchedFromUid; + mCallingPackage = r.launchedFromPackage; + mCallingFeatureId = r.launchedFromFeatureId; + setIntent(intent != null ? intent : r.intent, info != null ? info : r.info); + return; + } setLockTaskAuth(r); } @@ -1271,13 +1268,7 @@ class Task extends WindowContainer<WindowContainer> { private void setIntent(Intent _intent, ActivityInfo info) { if (!isLeafTask()) return; - if (intent == null) { - mNeverRelinquishIdentity = - (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; - } else if (mNeverRelinquishIdentity) { - return; - } - + mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; affinity = info.taskAffinity; if (intent == null) { // If this task already has an intent associated with it, don't set the root @@ -3366,15 +3357,6 @@ class Task extends WindowContainer<WindowContainer> { return isResizeable(); } - /** - * Prepares the task bounds to be frozen with the current size. See - * {@link ActivityRecord#freezeBounds}. - */ - void prepareFreezingBounds() { - mPreparedFrozenBounds.set(getBounds()); - mPreparedFrozenMergedConfig.setTo(getConfiguration()); - } - @Override void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets, Rect outSurfaceInsets) { @@ -3644,7 +3626,6 @@ class Task extends WindowContainer<WindowContainer> { @Override void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { - if (isOrganized()) return; super.resetSurfacePositionForAnimationLeash(t); } @@ -5061,6 +5042,10 @@ class Task extends WindowContainer<WindowContainer> { } } else { // No longer managed by any organizer. + final TaskDisplayArea taskDisplayArea = getDisplayArea(); + if (taskDisplayArea != null) { + taskDisplayArea.removeLaunchRootTask(this); + } setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */); if (mCreatedByOrganizer) { removeImmediately("setTaskOrganizer"); @@ -7364,6 +7349,7 @@ class Task extends WindowContainer<WindowContainer> { return reuseOrCreateTask(info, intent, null /*voiceSession*/, null /*voiceInteractor*/, toTop, null /*activity*/, null /*source*/, null /*options*/); } + // TODO: Can be removed once we change callpoints creating root tasks to be creating tasks. /** Either returns this current task to be re-used or creates a new child task. */ Task reuseOrCreateTask(ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, @@ -7371,7 +7357,7 @@ class Task extends WindowContainer<WindowContainer> { ActivityRecord source, ActivityOptions options) { Task task; - if (DisplayContent.canReuseExistingTask(getWindowingMode(), getActivityType())) { + if (canReuseAsLeafTask()) { // This root task will only contain one task, so just return itself since all root // tasks ara now tasks and all tasks are now root tasks. task = reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity); @@ -7406,10 +7392,24 @@ class Task extends WindowContainer<WindowContainer> { return task; } + /** Return {@code true} if this task can be reused as leaf task. */ + private boolean canReuseAsLeafTask() { + // Cannot be reused as leaf task if this task is created by organizer or having child tasks. + if (mCreatedByOrganizer || !isLeafTask()) { + return false; + } + + // Existing Tasks can be reused if a new root task will be created anyway, or for the + // Dream - because there can only ever be one DreamActivity. + final int windowingMode = getWindowingMode(); + final int activityType = getActivityType(); + return DisplayContent.alwaysCreateRootTask(windowingMode, activityType) + || activityType == ACTIVITY_TYPE_DREAM; + } + void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { Task task = child.asTask(); try { - if (task != null) { task.setForceShowForAllUsers(showForAllUsers); } @@ -7502,10 +7502,6 @@ class Task extends WindowContainer<WindowContainer> { }); } - void prepareFreezingTaskBounds() { - forAllLeafTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */); - } - private int setBounds(Rect existing, Rect bounds) { if (equivalentBounds(existing, bounds)) { return BOUNDS_CHANGE_NONE; diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index badd7fda2897..76869e548fce 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1135,12 +1135,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { "Can't set not mCreatedByOrganizer as launch root tr=" + rootTask); } - LaunchRootTaskDef def = null; - for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) { - if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue; - def = mLaunchRootTasks.get(i); - } - + LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask); if (def != null) { // Remove so we add to the end of the list. mLaunchRootTasks.remove(def); @@ -1156,6 +1151,23 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } + void removeLaunchRootTask(Task rootTask) { + LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask); + if (def != null) { + mLaunchRootTasks.remove(def); + } + } + + private @Nullable LaunchRootTaskDef getLaunchRootTaskDef(Task rootTask) { + LaunchRootTaskDef def = null; + for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) { + if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue; + def = mLaunchRootTasks.get(i); + break; + } + return def; + } + Task getLaunchRootTask(int windowingMode, int activityType, ActivityOptions options) { // Try to use the launch root task in options if available. if (options != null) { diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index f3b69e30b40a..ee5c1f014895 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -165,6 +165,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // hasInitialBounds is set if either activity options or layout has specified bounds. If // that's set we'll skip some adjustments later to avoid overriding the initial bounds. boolean hasInitialBounds = false; + // hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow is set if the outParams.mBounds + // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is + // different, we should recalculating the bounds. + boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false; final boolean canApplyFreeformPolicy = canApplyFreeformWindowPolicy(display, launchMode); if (mSupervisor.canUseActivityOptionsLaunchBounds(options) && (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) { @@ -180,11 +184,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } else if (launchMode == WINDOWING_MODE_FULLSCREEN) { if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds); } else if (layout != null && canApplyFreeformPolicy) { - getLayoutBounds(display, root, layout, mTmpBounds); + mTmpBounds.set(currentParams.mBounds); + getLayoutBounds(suggestedDisplayArea, root, layout, mTmpBounds); if (!mTmpBounds.isEmpty()) { launchMode = WINDOWING_MODE_FREEFORM; outParams.mBounds.set(mTmpBounds); hasInitialBounds = true; + hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = true; if (DEBUG) appendLog("bounds-from-layout=" + outParams.mBounds); } else { if (DEBUG) appendLog("empty-window-layout"); @@ -240,6 +246,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // such as orientation. Otherwise, the app is forcefully launched in maximized. The rest of // this step is to define the default policy when there is no initial bounds or a fully // resolved current params from callers. + + // hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay is set if the outParams.mBounds + // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is + // different, we should recalcuating the bounds. + boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = false; if (display.inFreeformWindowingMode()) { if (launchMode == WINDOWING_MODE_PINNED) { if (DEBUG) appendLog("picture-in-picture"); @@ -247,8 +258,9 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea)) { launchMode = WINDOWING_MODE_FREEFORM; if (outParams.mBounds.isEmpty()) { - getTaskBounds(root, display, layout, launchMode, hasInitialBounds, - outParams.mBounds); + getTaskBounds(root, suggestedDisplayArea, layout, launchMode, + hasInitialBounds, outParams.mBounds); + hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = true; } if (DEBUG) appendLog("unresizable-freeform"); } else { @@ -287,6 +299,20 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { mTmpDisplayArea = displayArea; return true; }); + // We may need to recalculate the bounds if the new TaskDisplayArea is different from + // the suggested one we used to calculate the bounds. + if (mTmpDisplayArea != null && mTmpDisplayArea != suggestedDisplayArea) { + if (hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow) { + outParams.mBounds.setEmpty(); + getLayoutBounds(mTmpDisplayArea, root, layout, outParams.mBounds); + hasInitialBounds = !outParams.mBounds.isEmpty(); + } else if (hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay) { + outParams.mBounds.setEmpty(); + getTaskBounds(root, mTmpDisplayArea, layout, launchMode, + hasInitialBounds, outParams.mBounds); + } + } + if (mTmpDisplayArea != null) { taskDisplayArea = mTmpDisplayArea; mTmpDisplayArea = null; @@ -302,7 +328,6 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (phase == PHASE_DISPLAY_AREA) { return RESULT_CONTINUE; } - // TODO(b/152116619): Update the usages of display to use taskDisplayArea below. // STEP 4: Determine final launch bounds based on resolved windowing mode and activity // requested orientation. We set bounds to empty for fullscreen mode and keep bounds as is @@ -312,13 +337,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // We skip making adjustments if the params are fully resolved from previous results. if (fullyResolvedCurrentParam) { if (resolvedMode == WINDOWING_MODE_FREEFORM) { - // Make sure bounds are in the display if it's possibly in a different display/area. + // Make sure bounds are in the displayArea. if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) { - adjustBoundsToFitInDisplay(display, outParams.mBounds); + adjustBoundsToFitInDisplayArea(taskDisplayArea, outParams.mBounds); } // Even though we want to keep original bounds, we still don't want it to stomp on // an existing task. - adjustBoundsToAvoidConflictInDisplay(display, outParams.mBounds); + adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds); } } else if (taskDisplayArea.inFreeformWindowingMode()) { if (source != null && source.inFreeformWindowingMode() @@ -327,9 +352,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { && source.getDisplayArea() == taskDisplayArea) { // Set bounds to be not very far from source activity. cascadeBounds(source.getConfiguration().windowConfiguration.getBounds(), - display, outParams.mBounds); + taskDisplayArea, outParams.mBounds); } - getTaskBounds(root, display, layout, resolvedMode, hasInitialBounds, outParams.mBounds); + getTaskBounds(root, taskDisplayArea, layout, resolvedMode, hasInitialBounds, + outParams.mBounds); } return RESULT_CONTINUE; } @@ -499,30 +525,36 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { && launchMode == WINDOWING_MODE_PINNED; } - private void getLayoutBounds(@NonNull DisplayContent display, @NonNull ActivityRecord root, - @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect outBounds) { + private void getLayoutBounds(@NonNull TaskDisplayArea displayArea, @NonNull ActivityRecord root, + @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect inOutBounds) { final int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK; final int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; if (!windowLayout.hasSpecifiedSize() && verticalGravity == 0 && horizontalGravity == 0) { - outBounds.setEmpty(); + inOutBounds.setEmpty(); return; } // Use stable frame instead of raw frame to avoid launching freeform windows on top of // stable insets, which usually are system widgets such as sysbar & navbar. - final Rect displayStableBounds = mTmpStableBounds; - display.getStableRect(displayStableBounds); - final int defaultWidth = displayStableBounds.width(); - final int defaultHeight = displayStableBounds.height(); + final Rect stableBounds = mTmpStableBounds; + displayArea.getStableRect(stableBounds); + final int defaultWidth = stableBounds.width(); + final int defaultHeight = stableBounds.height(); int width; int height; if (!windowLayout.hasSpecifiedSize()) { - outBounds.setEmpty(); - getTaskBounds(root, display, windowLayout, WINDOWING_MODE_FREEFORM, - /* hasInitialBounds */ false, outBounds); - width = outBounds.width(); - height = outBounds.height(); + if (!inOutBounds.isEmpty()) { + // If the bounds is resolved already and WindowLayout doesn't have any opinion on + // its size, use the already resolved size and apply the gravity to it. + width = inOutBounds.width(); + height = inOutBounds.height(); + } else { + getTaskBounds(root, displayArea, windowLayout, WINDOWING_MODE_FREEFORM, + /* hasInitialBounds */ false, inOutBounds); + width = inOutBounds.width(); + height = inOutBounds.height(); + } } else { width = defaultWidth; if (windowLayout.width > 0 && windowLayout.width < defaultWidth) { @@ -563,11 +595,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { fractionOfVerticalOffset = 0.5f; } - outBounds.set(0, 0, width, height); - outBounds.offset(displayStableBounds.left, displayStableBounds.top); + inOutBounds.set(0, 0, width, height); + inOutBounds.offset(stableBounds.left, stableBounds.top); final int xOffset = (int) (fractionOfHorizontalOffset * (defaultWidth - width)); final int yOffset = (int) (fractionOfVerticalOffset * (defaultHeight - height)); - outBounds.offset(xOffset, yOffset); + inOutBounds.offset(xOffset, yOffset); } private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity, @@ -575,13 +607,9 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (!mSupervisor.mService.mSupportsNonResizableMultiWindow || activity.isResizeable()) { return false; } - final DisplayContent display = displayArea.getDisplayContent(); - if (display == null) { - return false; - } final int displayOrientation = orientationFromBounds(displayArea.getBounds()); - final int activityOrientation = resolveOrientation(activity, display, + final int activityOrientation = resolveOrientation(activity, displayArea, displayArea.getBounds()); if (displayArea.getWindowingMode() == WINDOWING_MODE_FREEFORM && displayOrientation != activityOrientation) { @@ -631,19 +659,19 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return orientation; } - private void cascadeBounds(@NonNull Rect srcBounds, @NonNull DisplayContent display, + private void cascadeBounds(@NonNull Rect srcBounds, @NonNull TaskDisplayArea displayArea, @NonNull Rect outBounds) { outBounds.set(srcBounds); - float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT; + float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT; final int defaultOffset = (int) (CASCADING_OFFSET_DP * density + 0.5f); - display.getBounds(mTmpBounds); + displayArea.getBounds(mTmpBounds); final int dx = Math.min(defaultOffset, Math.max(0, mTmpBounds.right - srcBounds.right)); final int dy = Math.min(defaultOffset, Math.max(0, mTmpBounds.bottom - srcBounds.bottom)); outBounds.offset(dx, dy); } - private void getTaskBounds(@NonNull ActivityRecord root, @NonNull DisplayContent display, + private void getTaskBounds(@NonNull ActivityRecord root, @NonNull TaskDisplayArea displayArea, @NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds, @NonNull Rect inOutBounds) { if (resolvedMode == WINDOWING_MODE_FULLSCREEN) { @@ -662,7 +690,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return; } - final int orientation = resolveOrientation(root, display, inOutBounds); + final int orientation = resolveOrientation(root, displayArea, inOutBounds); if (orientation != SCREEN_ORIENTATION_PORTRAIT && orientation != SCREEN_ORIENTATION_LANDSCAPE) { throw new IllegalStateException( @@ -671,7 +699,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } // First we get the default size we want. - getDefaultFreeformSize(display, layout, orientation, mTmpBounds); + getDefaultFreeformSize(displayArea, layout, orientation, mTmpBounds); if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) { // We're here because either input parameters specified initial bounds, or the suggested // bounds have the same size of the default freeform size. We should use the suggested @@ -681,22 +709,24 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (DEBUG) appendLog("freeform-size-orientation-match=" + inOutBounds); } else { // Meh, orientation doesn't match. Let's rotate inOutBounds in-place. - centerBounds(display, inOutBounds.height(), inOutBounds.width(), inOutBounds); + centerBounds(displayArea, inOutBounds.height(), inOutBounds.width(), + inOutBounds); if (DEBUG) appendLog("freeform-orientation-mismatch=" + inOutBounds); } } else { // We are here either because there is no suggested bounds, or the suggested bounds is // a cascade from source activity. We should use the default freeform size and center it - // to the center of suggested bounds (or the display if no suggested bounds). The - // default size might be too big to center to source activity bounds in display, so we - // may need to move it back to the display. - centerBounds(display, mTmpBounds.width(), mTmpBounds.height(), inOutBounds); - adjustBoundsToFitInDisplay(display, inOutBounds); + // to the center of suggested bounds (or the displayArea if no suggested bounds). The + // default size might be too big to center to source activity bounds in displayArea, so + // we may need to move it back to the displayArea. + centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(), + inOutBounds); + adjustBoundsToFitInDisplayArea(displayArea, inOutBounds); if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds); } // Lastly we adjust bounds to avoid conflicts with other tasks as much as possible. - adjustBoundsToAvoidConflictInDisplay(display, inOutBounds); + adjustBoundsToAvoidConflictInDisplayArea(displayArea, inOutBounds); } private int convertOrientationToScreenOrientation(int orientation) { @@ -710,13 +740,14 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } } - private int resolveOrientation(@NonNull ActivityRecord root, @NonNull DisplayContent display, - @NonNull Rect bounds) { + private int resolveOrientation(@NonNull ActivityRecord root, + @NonNull TaskDisplayArea displayArea, @NonNull Rect bounds) { int orientation = resolveOrientation(root); if (orientation == SCREEN_ORIENTATION_LOCKED) { orientation = bounds.isEmpty() - ? convertOrientationToScreenOrientation(display.getConfiguration().orientation) + ? convertOrientationToScreenOrientation( + displayArea.getConfiguration().orientation) : orientationFromBounds(bounds); if (DEBUG) { appendLog(bounds.isEmpty() ? "locked-orientation-from-display=" + orientation @@ -736,19 +767,17 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return orientation; } - private void getDefaultFreeformSize(@NonNull DisplayContent display, + private void getDefaultFreeformSize(@NonNull TaskDisplayArea displayArea, @NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) { - // Default size, which is letterboxing/pillarboxing in display. That's to say the large - // dimension of default size is the small dimension of display size, and the small dimension - // of default size is calculated to keep the same aspect ratio as the display's. Here we use - // stable bounds of displays because that indicates the area that isn't occupied by system - // widgets (e.g. sysbar and navbar). - final Rect displayStableBounds = mTmpStableBounds; - display.getStableRect(displayStableBounds); - final int portraitHeight = - Math.min(displayStableBounds.width(), displayStableBounds.height()); - final int otherDimension = - Math.max(displayStableBounds.width(), displayStableBounds.height()); + // Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large + // dimension of default size is the small dimension of displayArea size, and the small + // dimension of default size is calculated to keep the same aspect ratio as the + // displayArea's. Here we use stable bounds of displayArea because that indicates the area + // that isn't occupied by system widgets (e.g. sysbar and navbar). + final Rect stableBounds = mTmpStableBounds; + displayArea.getStableRect(stableBounds); + final int portraitHeight = Math.min(stableBounds.width(), stableBounds.height()); + final int otherDimension = Math.max(stableBounds.width(), stableBounds.height()); final int portraitWidth = (portraitHeight * portraitHeight) / otherDimension; final int defaultWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitHeight : portraitWidth; @@ -757,7 +786,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // Get window size based on Nexus 5x screen, we assume that this is enough to show content // of activities. - final float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT; + final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT; final int phonePortraitWidth = (int) (DEFAULT_PORTRAIT_PHONE_WIDTH_DP * density + 0.5f); final int phonePortraitHeight = (int) (DEFAULT_PORTRAIT_PHONE_HEIGHT_DP * density + 0.5f); final int phoneWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? phonePortraitHeight @@ -774,83 +803,83 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight)); bounds.set(0, 0, width, height); - bounds.offset(displayStableBounds.left, displayStableBounds.top); + bounds.offset(stableBounds.left, stableBounds.top); } /** * Gets centered bounds of width x height. If inOutBounds is not empty, the result bounds - * centers at its center or display's app bounds center if inOutBounds is empty. + * centers at its center or displayArea's app bounds center if inOutBounds is empty. */ - private void centerBounds(@NonNull DisplayContent display, int width, int height, + private void centerBounds(@NonNull TaskDisplayArea displayArea, int width, int height, @NonNull Rect inOutBounds) { if (inOutBounds.isEmpty()) { - display.getStableRect(inOutBounds); + displayArea.getStableRect(inOutBounds); } final int left = inOutBounds.centerX() - width / 2; final int top = inOutBounds.centerY() - height / 2; inOutBounds.set(left, top, left + width, top + height); } - private void adjustBoundsToFitInDisplay(@NonNull DisplayContent display, + private void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea, @NonNull Rect inOutBounds) { - final Rect displayStableBounds = mTmpStableBounds; - display.getStableRect(displayStableBounds); + final Rect stableBounds = mTmpStableBounds; + displayArea.getStableRect(stableBounds); - if (displayStableBounds.width() < inOutBounds.width() - || displayStableBounds.height() < inOutBounds.height()) { - // There is no way for us to fit the bounds in the display without changing width - // or height. Just move the start to align with the display. + if (stableBounds.width() < inOutBounds.width() + || stableBounds.height() < inOutBounds.height()) { + // There is no way for us to fit the bounds in the displayArea without changing width + // or height. Just move the start to align with the displayArea. final int layoutDirection = mSupervisor.mRootWindowContainer.getConfiguration().getLayoutDirection(); final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL - ? displayStableBounds.right - inOutBounds.right + inOutBounds.left - : displayStableBounds.left; - inOutBounds.offsetTo(left, displayStableBounds.top); + ? stableBounds.right - inOutBounds.right + inOutBounds.left + : stableBounds.left; + inOutBounds.offsetTo(left, stableBounds.top); return; } final int dx; - if (inOutBounds.right > displayStableBounds.right) { - // Right edge is out of display. - dx = displayStableBounds.right - inOutBounds.right; - } else if (inOutBounds.left < displayStableBounds.left) { - // Left edge is out of display. - dx = displayStableBounds.left - inOutBounds.left; + if (inOutBounds.right > stableBounds.right) { + // Right edge is out of displayArea. + dx = stableBounds.right - inOutBounds.right; + } else if (inOutBounds.left < stableBounds.left) { + // Left edge is out of displayArea. + dx = stableBounds.left - inOutBounds.left; } else { - // Vertical edges are all in display. + // Vertical edges are all in displayArea. dx = 0; } final int dy; - if (inOutBounds.top < displayStableBounds.top) { - // Top edge is out of display. - dy = displayStableBounds.top - inOutBounds.top; - } else if (inOutBounds.bottom > displayStableBounds.bottom) { - // Bottom edge is out of display. - dy = displayStableBounds.bottom - inOutBounds.bottom; + if (inOutBounds.top < stableBounds.top) { + // Top edge is out of displayArea. + dy = stableBounds.top - inOutBounds.top; + } else if (inOutBounds.bottom > stableBounds.bottom) { + // Bottom edge is out of displayArea. + dy = stableBounds.bottom - inOutBounds.bottom; } else { - // Horizontal edges are all in display. + // Horizontal edges are all in displayArea. dy = 0; } inOutBounds.offset(dx, dy); } /** - * Adjusts input bounds to avoid conflict with existing tasks in the display. + * Adjusts input bounds to avoid conflict with existing tasks in the displayArea. * * If the input bounds conflict with existing tasks, this method scans the bounds in a series of - * directions to find a location where the we can put the bounds in display without conflict + * directions to find a location where the we can put the bounds in displayArea without conflict * with any other tasks. * - * It doesn't try to adjust bounds that's not fully in the given display. + * It doesn't try to adjust bounds that's not fully in the given displayArea. * - * @param display the display which tasks are to check + * @param displayArea the displayArea which tasks are to check * @param inOutBounds the bounds used to input initial bounds and output result bounds */ - private void adjustBoundsToAvoidConflictInDisplay(@NonNull DisplayContent display, + private void adjustBoundsToAvoidConflictInDisplayArea(@NonNull TaskDisplayArea displayArea, @NonNull Rect inOutBounds) { final List<Rect> taskBoundsToCheck = new ArrayList<>(); - display.forAllRootTasks(task -> { + displayArea.forAllRootTasks(task -> { if (!task.inFreeformWindowingMode()) { return; } @@ -859,28 +888,28 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { taskBoundsToCheck.add(task.getChildAt(j).getBounds()); } }, false /* traverseTopToBottom */); - adjustBoundsToAvoidConflict(display.getBounds(), taskBoundsToCheck, inOutBounds); + adjustBoundsToAvoidConflict(displayArea.getBounds(), taskBoundsToCheck, inOutBounds); } /** - * Adjusts input bounds to avoid conflict with provided display bounds and list of tasks bounds - * for the display. + * Adjusts input bounds to avoid conflict with provided displayArea bounds and list of tasks + * bounds for the displayArea. * * Scans the bounds in directions to find a candidate location that does not conflict with the - * provided list of task bounds. If starting bounds are outside the display bounds or if no + * provided list of task bounds. If starting bounds are outside the displayArea bounds or if no * suitable candidate bounds are found, the method returns the input bounds. * - * @param displayBounds display bounds used to restrict the candidate bounds + * @param displayAreaBounds displayArea bounds used to restrict the candidate bounds * @param taskBoundsToCheck list of task bounds to check for conflict * @param inOutBounds the bounds used to input initial bounds and output result bounds */ @VisibleForTesting - void adjustBoundsToAvoidConflict(@NonNull Rect displayBounds, + void adjustBoundsToAvoidConflict(@NonNull Rect displayAreaBounds, @NonNull List<Rect> taskBoundsToCheck, @NonNull Rect inOutBounds) { - if (!displayBounds.contains(inOutBounds)) { - // The initial bounds are already out of display. The scanning algorithm below doesn't - // work so well with them. + if (!displayAreaBounds.contains(inOutBounds)) { + // The initial bounds are already out of displayArea. The scanning algorithm below + // doesn't work so well with them. return; } @@ -890,7 +919,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return; } - calculateCandidateShiftDirections(displayBounds, inOutBounds); + calculateCandidateShiftDirections(displayAreaBounds, inOutBounds); for (int direction : mTmpDirections) { if (direction == Gravity.NO_GRAVITY) { // We exhausted candidate directions, give up. @@ -899,12 +928,12 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { mTmpBounds.set(inOutBounds); while (boundsConflict(taskBoundsToCheck, mTmpBounds) - && displayBounds.contains(mTmpBounds)) { - shiftBounds(direction, displayBounds, mTmpBounds); + && displayAreaBounds.contains(mTmpBounds)) { + shiftBounds(direction, displayAreaBounds, mTmpBounds); } if (!boundsConflict(taskBoundsToCheck, mTmpBounds) - && displayBounds.contains(mTmpBounds)) { + && displayAreaBounds.contains(mTmpBounds)) { // Found a candidate. Just use this. inOutBounds.set(mTmpBounds); if (DEBUG) appendLog("avoid-bounds-conflict=" + inOutBounds); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 98eb11f8a970..aadb2722a313 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -135,6 +135,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mSyncId = mSyncEngine.startSyncSet(this); } + @VisibleForTesting + int getSyncId() { + return mSyncId; + } + /** * Formally starts the transition. Participants can be collected before this is started, * but this won't consider itself ready until started -- even if all the participants have @@ -235,16 +240,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe for (int i = mTargets.size() - 1; i >= 0; --i) { final WindowContainer target = mTargets.valueAt(i); if (target.getParent() != null) { + final SurfaceControl targetLeash = getLeashSurface(target); + final SurfaceControl origParent = getOrigParentSurface(target); // Ensure surfaceControls are re-parented back into the hierarchy. - t.reparent(target.getSurfaceControl(), target.getParent().getSurfaceControl()); - t.setLayer(target.getSurfaceControl(), target.getLastLayer()); + t.reparent(targetLeash, origParent); + t.setLayer(targetLeash, target.getLastLayer()); // TODO(shell-transitions): Once all remotables have been moved, see if there is // a more appropriate place to do the following. This may // involve passing an SF transaction from shell on finish. target.getRelativePosition(tmpPos); - t.setPosition(target.getSurfaceControl(), tmpPos.x, tmpPos.y); - t.setCornerRadius(target.getSurfaceControl(), 0); - t.setShadowRadius(target.getSurfaceControl(), 0); + t.setPosition(targetLeash, tmpPos.x, tmpPos.y); + t.setCornerRadius(targetLeash, 0); + t.setShadowRadius(targetLeash, 0); displays.add(target.getDisplayContent()); } } @@ -271,12 +278,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Commit all going-invisible containers for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); - if (ar == null || ar.mVisibleRequested) { - continue; + if (ar != null && !ar.isVisibleRequested()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit activity becoming invisible: %s", ar); + ar.commitVisibility(false /* visible */, false /* performLayout */); + } + final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken(); + if (wt != null && !wt.isVisibleRequested()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit wallpaper becoming invisible: %s", ar); + wt.commitVisibility(false /* visible */); } - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " Commit activity becoming invisible: %s", ar); - ar.commitVisibility(false /* visible */, false /* performLayout */); } } @@ -455,8 +467,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final int depth = getChildDepth(topTargets.valueAt(j), sibling); if (depth < 0) continue; if (depth == 0) { - final int siblingMode = sibling.isVisibleRequested() - ? TRANSIT_OPEN : TRANSIT_CLOSE; + final int siblingMode = changes.get(sibling).getTransitMode(sibling); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " sibling is a top target with mode %s", TransitionInfo.modeToString(siblingMode)); @@ -638,6 +649,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } + /** Gets the leash surface for a window container */ + private static SurfaceControl getLeashSurface(WindowContainer wc) { + return wc.getSurfaceControl(); + } + + private static SurfaceControl getOrigParentSurface(WindowContainer wc) { + return wc.getParent().getSurfaceControl(); + } + /** * Construct a TransitionInfo object from a set of targets and changes. Also populates the * root surface. @@ -713,7 +733,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ChangeInfo info = changes.get(target); final TransitionInfo.Change change = new TransitionInfo.Change( target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() - : null, target.getSurfaceControl()); + : null, getLeashSurface(target)); // TODO(shell-transitions): Use leash for non-organized windows. if (info.mParent != null) { change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 5f46ffe604a6..6338f39e2e67 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -69,7 +69,7 @@ class TransitionController { * Creates a transition. It can immediately collect participants. */ @NonNull - Transition createTransition(@WindowManager.TransitionOldType int type, + Transition createTransition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags) { if (mTransitionPlayer == null) { throw new IllegalStateException("Shell Transitions not enabled"); @@ -113,6 +113,14 @@ class TransitionController { } /** + * @return {@code true} if transition is actively collecting changes and `wc` is one of them. + * This is {@code false} once a transition is playing. + */ + boolean isCollecting(@NonNull WindowContainer wc) { + return mCollectingTransition != null && mCollectingTransition.mParticipants.contains(wc); + } + + /** * @return {@code true} if transition is actively playing. This is not necessarily {@code true} * during collection. */ @@ -128,9 +136,7 @@ class TransitionController { /** @return {@code true} if wc is in a participant subtree */ boolean inTransition(@NonNull WindowContainer wc) { - if (mCollectingTransition != null && mCollectingTransition.mParticipants.contains(wc)) { - return true; - } + if (isCollecting(wc)) return true; for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { for (WindowContainer p = wc; p != null; p = p.getParent()) { if (mPlayingTransitions.get(i).mParticipants.contains(p)) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 1a3138d492c8..7c5afa8282ee 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -126,12 +126,18 @@ class WallpaperController { } mFindResults.resetTopWallpaper = true; - if (w.mActivityRecord != null && !w.mActivityRecord.isVisible() - && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) { - - // If this window's app token is hidden and not animating, it is of no interest to us. - if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w); - return false; + if (mService.mAtmService.getTransitionController().getTransitionPlayer() == null) { + if (w.mActivityRecord != null && !w.mActivityRecord.isVisible() + && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) { + // If this window's app token is hidden and not animating, it is of no interest. + if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w); + return false; + } + } else { + if (w.mActivityRecord != null && !w.mActivityRecord.isVisibleRequested()) { + // An activity that is not going to remain visible shouldn't be the target. + return false; + } } if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen() + " mDrawState=" + w.mWinAnimator.mDrawState); @@ -227,7 +233,10 @@ class WallpaperController { } boolean isWallpaperVisible() { - return isWallpaperVisible(mWallpaperTarget); + for (int i = mWallpaperTokens.size() - 1; i >= 0; --i) { + if (mWallpaperTokens.get(i).isVisible()) return true; + } + return false; } /** @@ -240,7 +249,7 @@ class WallpaperController { } } - private boolean isWallpaperVisible(WindowState wallpaperTarget) { + private boolean shouldWallpaperBeVisible(WindowState wallpaperTarget) { if (DEBUG_WALLPAPER) { Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev=" + mPrevWallpaperTarget); @@ -255,18 +264,18 @@ class WallpaperController { } void updateWallpaperVisibility() { - final boolean visible = isWallpaperVisible(mWallpaperTarget); + final boolean visible = shouldWallpaperBeVisible(mWallpaperTarget); for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.updateWallpaperVisibility(visible); + token.setVisibility(visible); } } - void hideDeferredWallpapersIfNeeded() { - if (mDeferredHideWallpaper != null) { - hideWallpapers(mDeferredHideWallpaper); - mDeferredHideWallpaper = null; + void hideDeferredWallpapersIfNeededLegacy() { + for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { + final WallpaperWindowToken token = mWallpaperTokens.get(i); + token.commitVisibility(false); } } @@ -275,18 +284,9 @@ class WallpaperController { && (mWallpaperTarget != winGoingAway || mPrevWallpaperTarget != null)) { return; } - if (mWallpaperTarget != null - && mWallpaperTarget.getDisplayContent().mAppTransition.isRunning()) { - // Defer hiding the wallpaper when app transition is running until the animations - // are done. - mDeferredHideWallpaper = winGoingAway; - return; - } - - final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway); for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); - token.hideWallpaperToken(wasDeferred, "hideWallpapers"); + token.setVisibility(false); if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) { Slog.d(TAG, "Hiding wallpaper " + token + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev=" @@ -616,7 +616,7 @@ class WallpaperController { // The window is visible to the compositor...but is it visible to the user? // That is what the wallpaper cares about. - final boolean visible = mWallpaperTarget != null && isWallpaperVisible(mWallpaperTarget); + final boolean visible = mWallpaperTarget != null; if (DEBUG_WALLPAPER) { Slog.v(TAG, "Wallpaper visibility: " + visible + " at display " + mDisplayContent.getDisplayId()); diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 43303d4a5d7e..717775605c94 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -19,7 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -34,6 +34,8 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.Animation; +import com.android.internal.protolog.common.ProtoLog; + import java.util.function.Consumer; /** @@ -43,6 +45,8 @@ class WallpaperWindowToken extends WindowToken { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM; + private boolean mVisibleRequested = false; + WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens) { this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */); @@ -57,18 +61,16 @@ class WallpaperWindowToken extends WindowToken { } @Override + WallpaperWindowToken asWallpaperToken() { + return this; + } + + @Override void setExiting() { super.setExiting(); mDisplayContent.mWallpaperController.removeWallpaperToken(this); } - void hideWallpaperToken(boolean wasDeferred, String reason) { - for (int j = mChildren.size() - 1; j >= 0; j--) { - final WindowState wallpaper = mChildren.get(j); - wallpaper.hideWallpaperWindow(wasDeferred, reason); - } - } - void sendWindowWallpaperCommand( String action, int x, int y, int z, Bundle extras, boolean sync) { for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { @@ -93,24 +95,6 @@ class WallpaperWindowToken extends WindowToken { } } - void updateWallpaperVisibility(boolean visible) { - if (isVisible() != visible) { - mWmService.mAtmService.getTransitionController().collect(this); - // Need to do a layout to ensure the wallpaper now has the correct size. - mDisplayContent.setLayoutNeeded(); - } - - final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; - for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { - final WindowState wallpaper = mChildren.get(wallpaperNdx); - if (visible) { - wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */); - } - - wallpaper.dispatchWallpaperVisibility(visible); - } - } - /** * Starts {@param anim} on all children. */ @@ -122,16 +106,16 @@ class WallpaperWindowToken extends WindowToken { } void updateWallpaperWindows(boolean visible) { - if (isVisible() != visible) { if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "Wallpaper token " + token + " visible=" + visible); - mWmService.mAtmService.getTransitionController().collect(this); - // Need to do a layout to ensure the wallpaper now has the correct size. - mDisplayContent.setLayoutNeeded(); + setVisibility(visible); } - final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; + if (mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null) { + return; + } + final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget(); if (visible && wallpaperTarget != null) { @@ -153,19 +137,52 @@ class WallpaperWindowToken extends WindowToken { } } - for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { - final WindowState wallpaper = mChildren.get(wallpaperNdx); + setVisible(visible); + } - if (visible) { - wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */); + private void setVisible(boolean visible) { + final boolean wasClientVisible = isClientVisible(); + setClientVisible(visible); + if (visible && !wasClientVisible) { + for (int i = mChildren.size() - 1; i >= 0; i--) { + final WindowState wallpaper = mChildren.get(i); + wallpaper.requestUpdateWallpaperIfNeeded(); } + } + } - // First, make sure the client has the current visibility state. - wallpaper.dispatchWallpaperVisibility(visible); + /** + * Sets the requested visibility of this token. The visibility may not be if this is part of a + * transition. In that situation, make sure to call {@link #commitVisibility} when done. + */ + void setVisibility(boolean visible) { + // Before setting mVisibleRequested so we can track changes. + mWmService.mAtmService.getTransitionController().collect(this); + + setVisibleRequested(visible); - if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win " - + wallpaper); + // If in a transition, defer commits for activities that are going invisible + if (!visible && (mWmService.mAtmService.getTransitionController().inTransition() + || getDisplayContent().mAppTransition.isRunning())) { + return; } + + commitVisibility(visible); + } + + /** + * Commits the visibility of this token. This will directly update the visibility without + * regard for other state (like being in a transition). + */ + void commitVisibility(boolean visible) { + if (visible == isVisible()) return; + + ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS, + "commitVisibility: %s: visible=%b mVisibleRequested=%b", this, + isVisible(), mVisibleRequested); + + setVisibleRequested(visible); + setVisible(visible); } @Override @@ -186,9 +203,10 @@ class WallpaperWindowToken extends WindowToken { } boolean hasVisibleNotDrawnWallpaper() { + if (!isVisible()) return false; for (int j = mChildren.size() - 1; j >= 0; --j) { final WindowState wallpaper = mChildren.get(j); - if (wallpaper.hasVisibleNotDrawnWallpaper()) { + if (!wallpaper.isDrawn() && wallpaper.isVisible()) { return true; } } @@ -210,6 +228,21 @@ class WallpaperWindowToken extends WindowToken { return false; } + void setVisibleRequested(boolean visible) { + if (mVisibleRequested == visible) return; + mVisibleRequested = visible; + setInsetsFrozen(!visible); + } + + @Override + boolean isVisibleRequested() { + return mVisibleRequested; + } + + @Override + boolean isVisible() { + return isClientVisible(); + } @Override public String toString() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index dd4ee877c05b..0c4ff2fe6365 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2684,14 +2684,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Nullable ArrayList<WindowContainer> sources) { final Task task = asTask(); if (task != null && !enter && !task.isHomeOrRecentsRootTask()) { - if (AppTransition.isClosingTransitOld(transit)) { - // Freezes the insets state when the window is in app exiting transition, to - // ensure the exiting window won't receive unexpected insets changes from the - // next window. - task.forAllWindows(w -> { - w.freezeInsetsState(); - }, true /* traverseTopToBottom */); - } mDisplayContent.showImeScreenshot(); } final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, @@ -3068,6 +3060,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** Cheap way of doing cast and instanceof. */ + WallpaperWindowToken asWallpaperToken() { + return null; + } + + /** Cheap way of doing cast and instanceof. */ DisplayArea asDisplayArea() { return null; } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index e183ea0d81ab..7450782364f4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -66,7 +66,7 @@ public abstract class WindowManagerInternal { /** * Is trace enabled or not. */ - boolean isEnabled(); + boolean isAccessibilityTracingEnabled(); /** * Add an accessibility trace entry. @@ -667,4 +667,13 @@ public abstract class WindowManagerInternal { * Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}. */ public abstract void moveWindowTokenToDisplay(IBinder binder, int displayId); + + /** + * Checks whether the given window should restore the last IME visibility. + * + * @param imeTargetWindowToken The token of the (IME target) window + * @return {@code true} when the system allows to restore the IME visibility, + * {@code false} otherwise. + */ + public abstract boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 518176b2ef75..04a560b21da3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -223,6 +223,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IAppTransitionAnimationSpecsFuture; +import android.view.ICrossWindowBlurEnabledListener; import android.view.IDisplayFoldListener; import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowListener; @@ -462,12 +463,12 @@ public class WindowManagerService extends IWindowManager.Stub private static final int ANIMATION_COMPLETED_TIMEOUT_MS = 5000; /** - * Override of task letterbox aspect ratio that is set via ADB with - * set-task-letterbox-aspect-ratio or via {@link - * com.android.internal.R.dimen.config_taskLetterboxAspectRatio} will be ignored + * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with + * set-fixed-orientation-letterbox-aspect-ratio or via {@link + * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored * if it is <= this value. */ - static final float MIN_TASK_LETTERBOX_ASPECT_RATIO = 1.0f; + static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f; @VisibleForTesting WindowManagerConstants mConstants; @@ -756,6 +757,8 @@ public class WindowManagerService extends IWindowManager.Stub final TaskSnapshotController mTaskSnapshotController; + final BlurController mBlurController = new BlurController(); + boolean mIsTouchDevice; boolean mIsFakeTouchDevice; @@ -1000,9 +1003,9 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mAnimationsDisabled = false; boolean mPointerLocationEnabled = false; - // Aspect ratio of task level letterboxing, values <= MIN_TASK_LETTERBOX_ASPECT_RATIO will be - // ignored. - private volatile float mTaskLetterboxAspectRatio; + // Aspect ratio of letterbox for fixed orientation, values <= + // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. + private volatile float mFixedOrientationLetterboxAspectRatio; /** Enum for Letterbox background type. */ @Retention(RetentionPolicy.SOURCE) @@ -1253,8 +1256,8 @@ public class WindowManagerService extends IWindowManager.Stub mAssistantOnTopOfDream = context.getResources().getBoolean( com.android.internal.R.bool.config_assistantOnTopOfDream); - mTaskLetterboxAspectRatio = context.getResources().getFloat( - com.android.internal.R.dimen.config_taskLetterboxAspectRatio); + mFixedOrientationLetterboxAspectRatio = context.getResources().getFloat( + com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio); mLetterboxActivityCornersRadius = context.getResources().getInteger( com.android.internal.R.integer.config_letterboxActivityCornersRadius); mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor( @@ -3850,29 +3853,29 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Overrides the aspect ratio of task level letterboxing. If given value is <= {@link - * #MIN_TASK_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link - * com.android.internal.R.dimen.config_taskLetterboxAspectRatio} will be ignored and + * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link + * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link + * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and * the framework implementation will be used to determine the aspect ratio. */ - void setTaskLetterboxAspectRatio(float aspectRatio) { - mTaskLetterboxAspectRatio = aspectRatio; + void setFixedOrientationLetterboxAspectRatio(float aspectRatio) { + mFixedOrientationLetterboxAspectRatio = aspectRatio; } /** - * Resets the aspect ratio of task level letterboxing to {@link - * com.android.internal.R.dimen.config_taskLetterboxAspectRatio}. + * Resets the aspect ratio of letterbox for fixed orientation to {@link + * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}. */ - void resetTaskLetterboxAspectRatio() { - mTaskLetterboxAspectRatio = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_taskLetterboxAspectRatio); + void resetFixedOrientationLetterboxAspectRatio() { + mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio); } /** - * Gets the aspect ratio of task level letterboxing. + * Gets the aspect ratio of letterbox for fixed orientation. */ - float getTaskLetterboxAspectRatio() { - return mTaskLetterboxAspectRatio; + float getFixedOrientationLetterboxAspectRatio() { + return mFixedOrientationLetterboxAspectRatio; } /** @@ -3968,6 +3971,21 @@ public class WindowManagerService extends IWindowManager.Stub ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR; } + /** Returns a string representing the given {@link LetterboxBackgroundType}. */ + static String letterboxBackgroundTypeToString( + @LetterboxBackgroundType int backgroundType) { + switch (backgroundType) { + case LETTERBOX_BACKGROUND_SOLID_COLOR: + return "LETTERBOX_BACKGROUND_SOLID_COLOR"; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: + return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND"; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: + return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING"; + default: + return "unknown=" + backgroundType; + } + } + @Override public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) { if (!checkCallingPermission( @@ -5672,6 +5690,18 @@ public class WindowManagerService extends IWindowManager.Stub return mWindowTracing.isEnabled(); } + @Override + public boolean registerCrossWindowBlurEnabledListener( + ICrossWindowBlurEnabledListener listener) { + return mBlurController.registerCrossWindowBlurEnabledListener(listener); + } + + @Override + public void unregisterCrossWindowBlurEnabledListener( + ICrossWindowBlurEnabledListener listener) { + mBlurController.unregisterCrossWindowBlurEnabledListener(listener); + } + // ------------------------------------------------------------- // Internals // ------------------------------------------------------------- @@ -6411,6 +6441,7 @@ public class WindowManagerService extends IWindowManager.Stub } }); pw.print(" mInTouchMode="); pw.println(mInTouchMode); + pw.print(" mBlurEnabled="); pw.println(mBlurController.mBlurEnabled); pw.print(" mLastDisplayFreezeDuration="); TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw); if ( mLastFinishedFreezeSource != null) { @@ -8008,6 +8039,11 @@ public class WindowManagerService extends IWindowManager.Stub return dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getName(); } } + + @Override + public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken); + } } void registerAppFreezeListener(AppFreezeListener listener) { @@ -8665,6 +8701,22 @@ public class WindowManagerService extends IWindowManager.Stub boundsInWindow, hashAlgorithm, callback); } + boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + synchronized (mGlobalLock) { + final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken); + if (imeTargetWindow == null) { + return false; + } + final Task imeTargetWindowTask = imeTargetWindow.getTask(); + if (imeTargetWindowTask == null) { + return false; + } + final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId, + false /* isLowResolution */); + return snapshot != null && snapshot.hasImeSurface(); + } + } + private void sendDisplayHashError(RemoteCallback callback, int errorCode) { Bundle bundle = new Bundle(); bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode); diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 645786c02fc0..a46a8d56e226 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -117,10 +117,10 @@ public class WindowManagerShellCommand extends ShellCommand { return runGetIgnoreOrientationRequest(pw); case "dump-visible-window-views": return runDumpVisibleWindowViews(pw); - case "set-task-letterbox-aspect-ratio": - return runSetTaskLetterboxAspectRatio(pw); - case "get-task-letterbox-aspect-ratio": - return runGetTaskLetterboxAspectRatio(pw); + case "set-fixed-orientation-letterbox-aspect-ratio": + return runSetFixedOrientationLetterboxAspectRatio(pw); + case "get-fixed-orientation-letterbox-aspect-ratio": + return runGetFixedOrientationLetterboxAspectRatio(pw); case "set-letterbox-activity-corners-radius": return runSetLetterboxActivityCornersRadius(pw); case "get-letterbox-activity-corners-radius": @@ -531,12 +531,12 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private int runSetTaskLetterboxAspectRatio(PrintWriter pw) throws RemoteException { + private int runSetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException { final float aspectRatio; try { String arg = getNextArgRequired(); if ("reset".equals(arg)) { - mInternal.resetTaskLetterboxAspectRatio(); + mInternal.resetFixedOrientationLetterboxAspectRatio(); return 0; } aspectRatio = Float.parseFloat(arg); @@ -549,13 +549,13 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } - mInternal.setTaskLetterboxAspectRatio(aspectRatio); + mInternal.setFixedOrientationLetterboxAspectRatio(aspectRatio); return 0; } - private int runGetTaskLetterboxAspectRatio(PrintWriter pw) throws RemoteException { - final float aspectRatio = mInternal.getTaskLetterboxAspectRatio(); - if (aspectRatio <= WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO) { + private int runGetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException { + final float aspectRatio = mInternal.getFixedOrientationLetterboxAspectRatio(); + if (aspectRatio <= WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) { pw.println("Letterbox aspect ratio is not set"); } else { pw.println("Letterbox aspect ratio is " + aspectRatio); @@ -692,8 +692,8 @@ public class WindowManagerShellCommand extends ShellCommand { // set-ignore-orientation-request mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */); - // set-task-letterbox-aspect-ratio - mInternal.resetTaskLetterboxAspectRatio(); + // set-fixed-orientation-letterbox-aspect-ratio + mInternal.resetFixedOrientationLetterboxAspectRatio(); // set-letterbox-activity-corners-radius mInternal.resetLetterboxActivityCornersRadius(); @@ -734,12 +734,12 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]"); pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] "); pw.println(" If app requested orientation should be ignored."); - pw.println(" set-task-letterbox-aspect-ratio [reset|aspectRatio]"); - pw.println(" get-task-letterbox-aspect-ratio"); - pw.println(" Aspect ratio of task level letterboxing. If aspectRatio <= " - + WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO); - pw.println(" both it and R.dimen.config_taskLetterboxAspectRatio will be ignored"); - pw.println(" and framework implementation will be used to determine aspect ratio."); + pw.println(" set-fixed-orientation-letterbox-aspect-ratio [reset|aspectRatio]"); + pw.println(" get-fixed-orientation-letterbox-aspect-ratio"); + pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= " + + WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will be"); + pw.println(" ignored and framework implementation will determine aspect ratio."); pw.println(" set-letterbox-activity-corners-radius [reset|cornersRadius]"); pw.println(" get-letterbox-activity-corners-radius"); pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,"); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 1fc7041c0fe2..1830c0758f9a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -141,11 +141,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT; @@ -358,7 +356,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean mForceHideNonSystemOverlayWindow; boolean mAppFreezing; boolean mHidden = true; // Used to determine if to show child windows. - boolean mWallpaperVisible; // for wallpaper, what was last vis report? private boolean mDragResizing; private boolean mDragResizingChangeReported = true; private int mResizeMode; @@ -797,7 +794,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@link InsetsStateController#notifyInsetsChanged}. */ boolean isReadyToDispatchInsetsState() { - return isVisible() && mFrozenInsetsState == null; + return isVisibleRequested() && mFrozenInsetsState == null; } void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation, @@ -1190,16 +1187,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP layoutYDiff = 0; } else { windowFrames.mContainingFrame.set(getBounds()); - if (mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) { - - // If the bounds are frozen, we still want to translate the window freely and only - // freeze the size. - Rect frozen = mActivityRecord.mFrozenBounds.peek(); - windowFrames.mContainingFrame.right = - windowFrames.mContainingFrame.left + frozen.width(); - windowFrames.mContainingFrame.bottom = - windowFrames.mContainingFrame.top + frozen.height(); - } // IME is up and obscuring this window. Adjust the window position so it is visible. if (isImeTarget) { if (inFreeformWindowingMode()) { @@ -1725,7 +1712,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean isVisibleRequested() { - return isVisible() && (mActivityRecord == null || mActivityRecord.isVisibleRequested()); + if (mToken != null && (mActivityRecord != null || mToken.asWallpaperToken() != null)) { + // Currently only ActivityRecord and WallpaperToken support visibleRequested. + return isVisible() && mToken.isVisibleRequested(); + } + return isVisible(); } /** @@ -1755,8 +1746,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@code false} otherwise. */ boolean wouldBeVisibleIfPolicyIgnored() { - return mHasSurface && !isParentWindowHidden() - && !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible); + if (!mHasSurface || isParentWindowHidden() || mAnimatingExit || mDestroying) { + return false; + } + final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null; + return !isWallpaper || mToken.isVisible(); } /** @@ -1814,6 +1808,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return ((!isParentWindowHidden() && atoken.isVisible()) || isAnimating(TRANSITION | PARENTS)); } + final WallpaperWindowToken wtoken = mToken.asWallpaperToken(); + if (wtoken != null) { + return !isParentWindowHidden() && wtoken.isVisible(); + } return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS); } @@ -1953,8 +1951,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // When there is keyguard, wallpaper could be placed over the secure app // window but invisible. We need to check wallpaper visibility explicitly // to determine if it's occluding apps. - return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE) - || (mIsWallpaper && mWallpaperVisible)) + final boolean isWallpaper = mToken != null && mToken.asWallpaperToken() != null; + return ((!isWallpaper && mAttrs.format == PixelFormat.OPAQUE) + || (isWallpaper && mToken.isVisible())) && isDrawn() && !isAnimating(TRANSITION | PARENTS); } @@ -2068,23 +2067,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP super.onResize(); } - void onUnfreezeBounds() { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowState c = mChildren.get(i); - c.onUnfreezeBounds(); - } - - if (!mHasSurface) { - return; - } - - mLayoutNeeded = true; - setDisplayLayoutNeeded(); - if (!mWmService.mResizingWindows.contains(this)) { - mWmService.mResizingWindows.add(this); - } - } - /** * If the window has moved due to its containing content frame changing, then notify the * listeners and optionally animate it. Simply checking a change of position is not enough, @@ -3251,7 +3233,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void sendAppVisibilityToClients() { super.sendAppVisibilityToClients(); - final boolean clientVisible = mActivityRecord.isClientVisible(); + if (mToken == null) return; + + final boolean clientVisible = mToken.isClientVisible(); + // TODO(shell-transitions): This is currently only applicable to app windows, BUT we + // want to extend the "starting" concept to other windows. if (mAttrs.type == TYPE_APPLICATION_STARTING && !clientVisible) { // Don't hide the starting window. return; @@ -3579,10 +3565,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public Configuration getConfiguration() { - if (mActivityRecord != null && mActivityRecord.mFrozenMergedConfig.size() > 0) { - return mActivityRecord.mFrozenMergedConfig.peek(); - } - // If the process has not registered to any display area to listen to the configuration // change, we can simply return the mFullConfiguration as default. if (!registeredForDisplayAreaConfigChanges()) { @@ -3639,9 +3621,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mActivityRecord != null && mActivityRecord.isRelaunching()) { return; } - // If the activity is invisible or going invisible, don't report either since it is going - // away. This is likely during a transition so we want to preserve the original state. - if (mActivityRecord != null && !mActivityRecord.isVisibleRequested()) { + // If this is an activity or wallpaper and is invisible or going invisible, don't report + // either since it is going away. This is likely during a transition so we want to preserve + // the original state. + if ((mActivityRecord != null || mToken.asWallpaperToken() != null) + && !mToken.isVisibleRequested()) { return; } @@ -3944,14 +3928,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return true; } - // If the bounds are currently frozen, it means that the layout size that the app sees - // and the bounds we clip this window to might be different. In order to avoid holes, we - // simulate that we are still resizing so the app fills the hole with the resizing - // background. - return (getDisplayContent().mDividerControllerLocked.isResizing() - || mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) && - !task.inFreeformWindowingMode() && !isGoneForLayout(); - + return getDisplayContent().mDividerControllerLocked.isResizing() + && !task.inFreeformWindowingMode() && !isGoneForLayout(); } void setDragResizing() { @@ -4061,8 +4039,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) { pw.println(prefix + "mIsImWindow=" + mIsImWindow + " mIsWallpaper=" + mIsWallpaper - + " mIsFloatingLayer=" + mIsFloatingLayer - + " mWallpaperVisible=" + mWallpaperVisible); + + " mIsFloatingLayer=" + mIsFloatingLayer); } if (dumpAll) { pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); @@ -4876,61 +4853,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; } - void hideWallpaperWindow(boolean wasDeferred, String reason) { - for (int j = mChildren.size() - 1; j >= 0; --j) { - final WindowState c = mChildren.get(j); - c.hideWallpaperWindow(wasDeferred, reason); - } - if (!mWinAnimator.mLastHidden || wasDeferred) { - mWinAnimator.hide(getGlobalTransaction(), reason); - getDisplayContent().mWallpaperController.mDeferredHideWallpaper = null; - dispatchWallpaperVisibility(false); - final DisplayContent displayContent = getDisplayContent(); - if (displayContent != null) { - displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - if (DEBUG_LAYOUT_REPEATS) { - mWmService.mWindowPlacerLocked.debugLayoutRepeats("hideWallpaperWindow " + this, - displayContent.pendingLayoutChanges); - } - } - } - } - - /** - * Check wallpaper window for visibility change and notify window if so. - * @param visible Current visibility. - */ - void dispatchWallpaperVisibility(final boolean visible) { - final boolean hideAllowed = - getDisplayContent().mWallpaperController.mDeferredHideWallpaper == null; - - // Only send notification if the visibility actually changed and we are not trying to hide - // the wallpaper when we are deferring hiding of the wallpaper. - if (mWallpaperVisible != visible && (hideAllowed || visible)) { - mWallpaperVisible = visible; - try { - if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Updating vis of wallpaper " + this - + ": " + visible + " from:\n" + Debug.getCallers(4, " ")); - mClient.dispatchAppVisibility(visible); - } catch (RemoteException e) { - } - } - } - - boolean hasVisibleNotDrawnWallpaper() { - if (mWallpaperVisible && !isDrawn()) { - return true; - } - for (int j = mChildren.size() - 1; j >= 0; --j) { - final WindowState c = mChildren.get(j); - if (c.hasVisibleNotDrawnWallpaper()) { - return true; - } - } - return false; - } - void updateReportedVisibility(UpdateReportedVisibilityResults results) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState c = mChildren.get(i); @@ -5273,22 +5195,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (!mAnimatingExit && mAppDied) { mIsDimming = true; getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW); - } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || (mAttrs.flags & FLAG_BLUR_BEHIND) != 0) + } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind()) && isVisibleNow() && !mHidden) { // Only show the Dimmer when the following is satisfied: - // 1. The window has the flag FLAG_DIM_BEHIND or background blur is requested + // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting. // 3. The WS is considered visible according to the isVisible() method // 4. The WS is not hidden. mIsDimming = true; final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0; - final int blurRadius = - (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 ? mAttrs.blurBehindRadius : 0; - getDimmer().dimBelow( - getSyncTransaction(), this, mAttrs.dimAmount, mAttrs.blurBehindRadius); + final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0; + getDimmer().dimBelow(getSyncTransaction(), this, dimAmount, blurRadius); } } + private boolean shouldDrawBlurBehind() { + return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mWmService.mBlurController.mBlurEnabled; + } + /** * Notifies SF about the priority of the window, if it changed. SF then uses this information * to decide which window's desired rendering rate should have a priority when deciding about diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index ece256e8c591..ebbebbb702d8 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -57,7 +57,6 @@ import android.content.Context; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.graphics.Region; import android.os.Debug; import android.os.Trace; import android.util.Slog; @@ -575,10 +574,7 @@ class WindowStateAnimator { setSurfaceBoundariesLocked(t); - if (mIsWallpaper && !w.mWallpaperVisible) { - // Wallpaper is no longer visible and there is no wp target => hide it. - hide(t, "prepareSurfaceLocked"); - } else if (w.isParentWindowHidden() || !w.isOnScreen()) { + if (w.isParentWindowHidden() || !w.isOnScreen()) { hide(t, "prepareSurfaceLocked"); mWallpaperControllerLocked.hideWallpapers(w); @@ -631,9 +627,6 @@ class WindowStateAnimator { if (showSurfaceRobustlyLocked(t)) { mAnimator.requestRemovalOfReplacedWindows(w); mLastHidden = false; - if (mIsWallpaper) { - w.dispatchWallpaperVisibility(true); - } final DisplayContent displayContent = w.getDisplayContent(); if (!displayContent.getLastHasContent()) { // This draw means the difference between unique content and mirroring. diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e87ee918e0f0..8867aa747379 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; @@ -112,6 +113,9 @@ class WindowToken extends WindowContainer<WindowState> { */ private final boolean mFromClientToken; + /** Have we told the window clients to show themselves? */ + private boolean mClientVisible; + /** * Used to fix the transform of the token to be rotated to a rotation different than it's * display. The window frames and surfaces corresponding to this token will be layouted and @@ -397,6 +401,21 @@ class WindowToken extends WindowContainer<WindowState> { return builder; } + boolean isClientVisible() { + return mClientVisible; + } + + void setClientVisible(boolean clientVisible) { + if (mClientVisible == clientVisible) { + return; + } + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible, + Debug.getCallers(5)); + mClientVisible = clientVisible; + sendAppVisibilityToClients(); + } + boolean hasFixedRotationTransform() { return mFixedRotationTransformState != null; } @@ -736,4 +755,13 @@ class WindowToken extends WindowContainer<WindowState> { boolean isFromClient() { return mFromClientToken; } + + /** @see WindowState#freezeInsetsState() */ + void setInsetsFrozen(boolean freeze) { + if (freeze) { + forAllWindows(WindowState::freezeInsetsState, true /* traverseTopToBottom */); + } else { + forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */); + } + } } diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index 7b78b8d23b4b..8efbaf577134 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -39,6 +39,8 @@ #include "dataloader.h" +// #define VERBOSE_READ_LOGS + namespace android { namespace { @@ -631,28 +633,65 @@ private: }; void onPageReadsWithUid(dataloader::PageReadsWithUid pageReads) final { + if (!pageReads.size()) { + return; + } + auto trace = atrace_is_tag_enabled(ATRACE_TAG); if (CC_LIKELY(!trace)) { return; } TracedRead last = {}; + auto lastSerialNo = mLastSerialNo; for (auto&& read : pageReads) { - if (read.id != last.fileId || read.uid != last.uid || - read.block != last.firstBlockIdx + last.count) { - traceRead(last); - last = TracedRead{ - .timestampUs = read.bootClockTsUs, - .fileId = read.id, - .uid = read.uid, - .firstBlockIdx = (uint32_t)read.block, - .count = 1, - }; - } else { + const auto expectedSerialNo = lastSerialNo + last.count; +#ifdef VERBOSE_READ_LOGS + { + FileIdx fileIdx = convertFileIdToFileIndex(read.id); + + auto appId = multiuser_get_app_id(read.uid); + auto userId = multiuser_get_user_id(read.uid); + auto trace = android::base:: + StringPrintf("verbose_page_read: serialNo=%lld (expected=%lld) index=%lld " + "file=%d appid=%d userid=%d", + static_cast<long long>(read.serialNo), + static_cast<long long>(expectedSerialNo), + static_cast<long long>(read.block), static_cast<int>(fileIdx), + static_cast<int>(appId), static_cast<int>(userId)); + + ATRACE_BEGIN(trace.c_str()); + ATRACE_END(); + } +#endif // VERBOSE_READ_LOGS + + if (read.serialNo == expectedSerialNo && read.id == last.fileId && + read.uid == last.uid && read.block == last.firstBlockIdx + last.count) { ++last.count; + continue; + } + + // First, trace the reads. + traceRead(last); + + // Second, report missing reads, if any. + if (read.serialNo != expectedSerialNo) { + const auto readsMissing = read.serialNo - expectedSerialNo; + traceMissingReads(readsMissing); } + + last = TracedRead{ + .timestampUs = read.bootClockTsUs, + .fileId = read.id, + .uid = read.uid, + .firstBlockIdx = (uint32_t)read.block, + .count = 1, + }; + lastSerialNo = read.serialNo; } + traceRead(last); + mLastSerialNo = lastSerialNo + last.count; } void traceRead(const TracedRead& read) { @@ -682,6 +721,13 @@ private: ATRACE_END(); } + void traceMissingReads(int64_t count) { + const auto trace = android::base::StringPrintf("missing_page_reads: count=%lld", + static_cast<long long>(count)); + ATRACE_BEGIN(trace.c_str()); + ATRACE_END(); + } + void receiver(unique_fd inout, MetadataMode mode) { std::vector<uint8_t> data; std::vector<IncFsDataBlock> instructions; @@ -828,6 +874,7 @@ private: std::atomic<bool> mStopReceiving = false; std::atomic<bool> mReadLogsEnabled = false; std::chrono::milliseconds mWaitOnEofInterval{WaitOnEofMinInterval}; + int64_t mLastSerialNo{1}; /** Tracks which files have been requested */ std::unordered_set<FileIdx> mRequestedFiles; }; diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index 89b931d35c40..ef2d0baff031 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -238,12 +238,12 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j return result.isOk() ? result.value().count() : -1; } -static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, - jobjectArray composition, jlong vibrationId) { +static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, + jobjectArray composition, jlong vibrationId) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorPerformComposedEffect failed because native wrapper was not initialized"); - return; + return -1; } size_t size = env->GetArrayLength(composition); std::vector<aidl::CompositeEffect> effects; @@ -252,7 +252,8 @@ static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong effects.push_back(effectFromJavaPrimitive(env, element)); } auto callback = wrapper->createCallback(vibrationId); - wrapper->hal()->performComposedEffect(effects, callback); + auto result = wrapper->hal()->performComposedEffect(effects, callback); + return result.isOk() ? result.value().count() : -1; } static jlong vibratorGetCapabilities(JNIEnv* env, jclass /* clazz */, jlong ptr) { @@ -296,7 +297,7 @@ static const JNINativeMethod method_table[] = { {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude}, {"vibratorPerformEffect", "(JJJJ)J", (void*)vibratorPerformEffect}, {"vibratorPerformComposedEffect", - "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)V", + "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J", (void*)vibratorPerformComposedEffect}, {"vibratorGetSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects}, {"vibratorGetSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives}, diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index c285ef519e44..6a8f6d419786 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -30,7 +30,6 @@ xsd_config { gen_writer: true, } - xsd_config { name: "display-device-config", srcs: ["display-device-config/display-device-config.xsd"], @@ -38,6 +37,12 @@ xsd_config { package_name: "com.android.server.display.config", } +xsd_config { + name: "display-layout-config", + srcs: ["display-layout-config/display-layout-config.xsd"], + api_dir: "display-layout-config/schema", + package_name: "com.android.server.display.config.layout", +} xsd_config { name: "cec-config", diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 7d705c1fac69..e4b961299f12 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -1,23 +1,24 @@ <?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 -<!-- This defines the format of the XML file generated by - ~ com.android.compat.annotation.ChangeIdProcessor annotation processor (from - ~ tools/platform-compat), and is parsed in com/android/server/compat/CompatConfig.java. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- + This defines the format of the XML file used to provide static configuration values + for the displays on a device. + It is parsed in com/android/server/display/DisplayDeviceConfig.java --> <xs:schema version="2.0" elementFormDefault="qualified" diff --git a/services/core/xsd/display-layout-config/OWNERS b/services/core/xsd/display-layout-config/OWNERS new file mode 100644 index 000000000000..20b75be9f11f --- /dev/null +++ b/services/core/xsd/display-layout-config/OWNERS @@ -0,0 +1,3 @@ +include /services/core/java/com/android/server/display/OWNERS + +flc@google.com diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd new file mode 100644 index 000000000000..c542c0d0c382 --- /dev/null +++ b/services/core/xsd/display-layout-config/display-layout-config.xsd @@ -0,0 +1,57 @@ +<?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. +--> + +<!-- + This defines the format of the XML file used to defines how displays are laid out + for a given device-state. + It is parsed in com/android/server/display/layout/DeviceStateToLayoutMap.java + More information on device-state can be found in DeviceStateManager.java +--> +<xs:schema version="2.0" + elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="layouts"> + <xs:complexType> + <xs:sequence> + <xs:element type="layout" name="layout" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + + <!-- Ensures only one layout is allowed per device state. --> + <xs:unique name="UniqueState"> + <xs:selector xpath="layout" /> + <xs:field xpath="@state" /> + </xs:unique> + </xs:element> + + <!-- Type definitions --> + + <xs:complexType name="layout"> + <xs:sequence> + <xs:element name="state" type="xs:nonNegativeInteger" /> + <xs:element name="display" type="display" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="display"> + <xs:sequence> + <xs:element name="address" type="xs:nonNegativeInteger"/> + </xs:sequence> + <xs:attribute name="enabled" type="xs:boolean" use="optional" /> + <xs:attribute name="isDefault" type="xs:boolean" use="optional" /> + </xs:complexType> +</xs:schema> diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt new file mode 100644 index 000000000000..817188509f81 --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/current.txt @@ -0,0 +1,34 @@ +// Signature format: 2.0 +package com.android.server.display.config.layout { + + public class Display { + ctor public Display(); + method public java.math.BigInteger getAddress(); + method public boolean getEnabled(); + method public boolean getIsDefault(); + method public void setAddress(java.math.BigInteger); + method public void setEnabled(boolean); + method public void setIsDefault(boolean); + } + + public class Layout { + ctor public Layout(); + method public java.util.List<com.android.server.display.config.layout.Display> getDisplay(); + method public java.math.BigInteger getState(); + method public void setState(java.math.BigInteger); + } + + public class Layouts { + ctor public Layouts(); + method public java.util.List<com.android.server.display.config.layout.Layout> getLayout(); + } + + public class XmlParser { + ctor public XmlParser(); + method public static com.android.server.display.config.layout.Layouts read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + +} + diff --git a/services/core/xsd/display-layout-config/schema/last_current.txt b/services/core/xsd/display-layout-config/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/last_current.txt diff --git a/services/core/xsd/display-layout-config/schema/last_removed.txt b/services/core/xsd/display-layout-config/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/last_removed.txt diff --git a/services/core/xsd/display-layout-config/schema/removed.txt b/services/core/xsd/display-layout-config/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/core/xsd/display-layout-config/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 28e9acf8d883..04af5c93160d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1583,8 +1583,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } public String[] getPersonalAppsForSuspension(int userId) { - return new PersonalAppsSuspensionHelper( - mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */)) + return PersonalAppsSuspensionHelper.forUser(mContext, userId) .getPersonalAppsForSuspension(); } @@ -1599,6 +1598,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { mSafetyChecker = safetyChecker; } + + void dumpPerUserData(IndentingPrintWriter pw, @UserIdInt int userId) { + PersonalAppsSuspensionHelper.forUser(mContext, userId).dump(pw); + } } /** @@ -9161,11 +9164,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void dumpDevicePolicyData(IndentingPrintWriter pw) { + private void dumpPerUserData(IndentingPrintWriter pw) { int userCount = mUserData.size(); - for (int u = 0; u < userCount; u++) { - DevicePolicyData policy = getUserData(mUserData.keyAt(u)); + for (int userId = 0; userId < userCount; userId++) { + DevicePolicyData policy = getUserData(mUserData.keyAt(userId)); policy.dump(pw); + pw.println(); + + pw.increaseIndent(); + mInjector.dumpPerUserData(pw, userId); + pw.decreaseIndent(); + pw.println(); } } @@ -9183,7 +9192,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.println(); mDeviceAdminServiceController.dump(pw); pw.println(); - dumpDevicePolicyData(pw); + dumpPerUserData(pw); pw.println(); mConstants.dump(pw); pw.println(); @@ -9229,20 +9238,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.increaseIndent(); dumpResources(pw, mContext, "cross_profile_apps", R.array.cross_profile_apps); dumpResources(pw, mContext, "vendor_cross_profile_apps", R.array.vendor_cross_profile_apps); + dumpResources(pw, mContext, "config_packagesExemptFromSuspension", + R.array.config_packagesExemptFromSuspension); pw.decreaseIndent(); pw.println(); } static void dumpResources(IndentingPrintWriter pw, Context context, String resName, int resId) { - String[] apps = context.getResources().getStringArray(resId); - if (apps == null || apps.length == 0) { - pw.printf("%s: empty\n", resName); + dumpApps(pw, resName, context.getResources().getStringArray(resId)); + } + + static void dumpApps(IndentingPrintWriter pw, String name, String[] apps) { + dumpApps(pw, name, Arrays.asList(apps)); + } + + static void dumpApps(IndentingPrintWriter pw, String name, List apps) { + if (apps == null || apps.isEmpty()) { + pw.printf("%s: empty\n", name); return; } - pw.printf("%s: %d app%s\n", resName, apps.length, apps.length == 1 ? "" : "s"); + int size = apps.size(); + pw.printf("%s: %d app%s\n", name, size, size == 1 ? "" : "s"); pw.increaseIndent(); - for (int i = 0; i < apps.length; i++) { - pw.printf("%d: %s\n", i, apps[i]); + for (int i = 0; i < size; i++) { + pw.printf("%d: %s\n", i, apps.get(i)); } pw.decreaseIndent(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java index c687184265c1..37dbfc170aff 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java @@ -20,6 +20,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -29,10 +30,12 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.IBinder; import android.os.ServiceManager; +import android.os.UserHandle; import android.provider.Settings; import android.provider.Telephony; import android.text.TextUtils; import android.util.ArraySet; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -49,7 +52,7 @@ import java.util.Set; /** * Utility class to find what personal apps should be suspended to limit personal device use. */ -public class PersonalAppsSuspensionHelper { +public final class PersonalAppsSuspensionHelper { private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG; // Flags to get all packages even if the user is still locked. @@ -60,9 +63,17 @@ public class PersonalAppsSuspensionHelper { private final PackageManager mPackageManager; /** + * Factory method + */ + public static PersonalAppsSuspensionHelper forUser(Context context, @UserIdInt int userId) { + return new PersonalAppsSuspensionHelper(context.createContextAsUser(UserHandle.of(userId), + /* flags= */ 0)); + } + + /** * @param context Context for the user whose apps should to be suspended. */ - public PersonalAppsSuspensionHelper(Context context) { + private PersonalAppsSuspensionHelper(Context context) { mContext = context; mPackageManager = context.getPackageManager(); } @@ -181,4 +192,21 @@ public class PersonalAppsSuspensionHelper { iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder); return new AccessibilityManager(mContext, service, userId); } + + void dump(IndentingPrintWriter pw) { + pw.println("PersonalAppsSuspensionHelper"); + pw.increaseIndent(); + + DevicePolicyManagerService.dumpApps(pw, "critical packages", getCriticalPackages()); + DevicePolicyManagerService.dumpApps(pw, "launcher packages", getSystemLauncherPackages()); + DevicePolicyManagerService.dumpApps(pw, "accessibility services", + getAccessibilityServices()); + DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages()); + pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext)); + pw.printf("Settings package: %s\n", getSettingsPackageName()); + DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension", + getPersonalAppsForSuspension()); + + pw.decreaseIndent(); + } } diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 0d878b401bee..a262939c0ef9 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -102,7 +102,7 @@ public final class ProfcollectForwardingService extends SystemService { return false; } try { - return !mIProfcollect.GetSupportedProvider().isEmpty(); + return !mIProfcollect.get_supported_provider().isEmpty(); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); return false; @@ -191,7 +191,7 @@ public final class ProfcollectForwardingService extends SystemService { } try { - sSelfService.mIProfcollect.ProcessProfile(); + sSelfService.mIProfcollect.process(false); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); } @@ -234,7 +234,7 @@ public final class ProfcollectForwardingService extends SystemService { if (DEBUG) { Log.d(LOG_TAG, "Tracing on app launch event: " + packageName); } - mIProfcollect.TraceOnce("applaunch"); + mIProfcollect.trace_once("applaunch"); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); } @@ -296,7 +296,7 @@ public final class ProfcollectForwardingService extends SystemService { } try { - mIProfcollect.CreateProfileReport(); + mIProfcollect.report(); } catch (RemoteException e) { Log.e(LOG_TAG, e.getMessage()); } 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 e99b07144853..0fa9a1def381 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 @@ -52,8 +52,10 @@ class DomainVerificationCollectorTest { val collector = mockCollector() assertThat(collector.collectAllWebDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com") - assertThat(collector.collectAutoVerifyDomains(pkg)) + assertThat(collector.collectValidAutoVerifyDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com", "example4.com") + assertThat(collector.collectInvalidAutoVerifyDomains(pkg)) + .containsExactly("invalid1", "invalid2", "invalid3", "invalid4") } @Test @@ -62,7 +64,8 @@ class DomainVerificationCollectorTest { val collector = mockCollector() assertThat(collector.collectAllWebDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com") - assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectValidAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectInvalidAutoVerifyDomains(pkg)).isEmpty() } @Test @@ -71,8 +74,10 @@ class DomainVerificationCollectorTest { val collector = mockCollector(linkedApps = setOf(TEST_PKG_NAME)) assertThat(collector.collectAllWebDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com") - assertThat(collector.collectAutoVerifyDomains(pkg)) + assertThat(collector.collectValidAutoVerifyDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com", "example4.com") + assertThat(collector.collectInvalidAutoVerifyDomains(pkg)) + .containsExactly("invalid1", "invalid2", "invalid3", "invalid4") } @Test @@ -92,6 +97,7 @@ class DomainVerificationCollectorTest { addDataScheme("https") addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example1.com", null) + addDataAuthority("invalid1", null) } ) }, @@ -111,6 +117,7 @@ class DomainVerificationCollectorTest { addDataScheme("nonWebScheme") addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example2.com", null) + addDataAuthority("invalid2", null) } ) }, @@ -122,7 +129,8 @@ class DomainVerificationCollectorTest { val collector = mockCollector() assertThat(collector.collectAllWebDomains(pkg)) .containsExactly("example1.com", "example2.com") - assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectValidAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectInvalidAutoVerifyDomains(pkg)).isEmpty() } @Test @@ -132,8 +140,10 @@ class DomainVerificationCollectorTest { assertThat(collector.collectAllWebDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com") - assertThat(collector.collectAutoVerifyDomains(pkg)) + assertThat(collector.collectValidAutoVerifyDomains(pkg)) .containsExactly("example1.com", "example3.com") + assertThat(collector.collectInvalidAutoVerifyDomains(pkg)) + .containsExactly("invalid1", "invalid3") } @Test @@ -143,7 +153,8 @@ class DomainVerificationCollectorTest { assertThat(collector.collectAllWebDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com") - assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectValidAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectInvalidAutoVerifyDomains(pkg)).isEmpty() } @Test @@ -153,7 +164,8 @@ class DomainVerificationCollectorTest { assertThat(collector.collectAllWebDomains(pkg)) .containsExactly("example1.com", "example2.com", "example3.com") - assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectValidAutoVerifyDomains(pkg)).isEmpty() + assertThat(collector.collectInvalidAutoVerifyDomains(pkg)).isEmpty() } private fun mockCollector(linkedApps: Set<String> = emptySet()): DomainVerificationCollector { @@ -178,6 +190,7 @@ class DomainVerificationCollectorTest { <data android:scheme="https"/> <data android:path="/sub"/> <data android:host="example1.com"/> + <data android:host="invalid1"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW"/> @@ -186,6 +199,7 @@ class DomainVerificationCollectorTest { <data android:scheme="http"/> <data android:path="/sub2"/> <data android:host="example2.com"/> + <data android:host="invalid2."/> </intent-filter> <intent-filter android:autoVerify="$autoVerify"> <action android:name="android.intent.action.VIEW"/> @@ -194,6 +208,7 @@ class DomainVerificationCollectorTest { <data android:scheme="https"/> <data android:path="/sub3"/> <data android:host="example3.com"/> + <data android:host=".invalid3"/> </intent-filter> <intent-filter android:autoVerify="$autoVerify"> <action android:name="android.intent.action.VIEW"/> @@ -201,6 +216,7 @@ class DomainVerificationCollectorTest { <data android:scheme="https"/> <data android:path="/sub4"/> <data android:host="example4.com"/> + <data android:host="invalid4"/> </intent-filter> <intent-filter android:autoVerify="$autoVerify"> <action android:name="android.intent.action.VIEW"/> @@ -208,6 +224,7 @@ class DomainVerificationCollectorTest { <data android:scheme="https"/> <data android:path="/sub5"/> <data android:host="example5.com"/> + <data android:host="invalid5"/> </intent-filter> <intent-filter android:autoVerify="$autoVerify"> <category android:name="android.intent.category.BROWSABLE"/> @@ -215,11 +232,12 @@ class DomainVerificationCollectorTest { <data android:scheme="https"/> <data android:path="/sub5"/> <data android:host="example5.com"/> + <data android:host="invalid5"/> </intent-filter> </xml> """.trimIndent() - return mockThrowOnUnmocked<AndroidPackage> { + return mockThrowOnUnmocked { whenever(packageName) { TEST_PKG_NAME } whenever(targetSdkVersion) { if (useV2) Build.VERSION_CODES.S else Build.VERSION_CODES.R @@ -238,6 +256,7 @@ class DomainVerificationCollectorTest { addDataScheme("https") addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example1.com", null) + addDataAuthority("invalid1", null) } ) addIntent( @@ -248,6 +267,7 @@ class DomainVerificationCollectorTest { addDataScheme("http") addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example2.com", null) + addDataAuthority("invalid2", null) } ) }, @@ -261,6 +281,7 @@ class DomainVerificationCollectorTest { addDataScheme("https") addDataPath("/sub3", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example3.com", null) + addDataAuthority("invalid3", null) } ) }, @@ -273,6 +294,7 @@ class DomainVerificationCollectorTest { addDataScheme("https") addDataPath("/sub4", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example4.com", null) + addDataAuthority("invalid4", null) } ) addIntent( @@ -283,6 +305,7 @@ class DomainVerificationCollectorTest { addDataScheme("https") addDataPath("/sub5", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example5.com", null) + addDataAuthority("invalid5", null) } ) addIntent( @@ -293,6 +316,7 @@ class DomainVerificationCollectorTest { addDataScheme("https") addDataPath("/sub6", PatternMatcher.PATTERN_LITERAL) addDataAuthority("example6.com", null) + addDataAuthority("invalid6", null) } ) }, 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 9447f390ada0..8ef92393242a 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,7 +19,7 @@ package com.android.server.pm.test.verify.domain 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.DomainVerificationUserSelection +import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Parcel import android.os.Parcelable import android.os.UserHandle @@ -28,7 +28,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import java.util.UUID -import kotlin.random.Random @RunWith(Parameterized::class) class DomainVerificationCoreApiTest { @@ -92,9 +91,9 @@ class DomainVerificationCoreApiTest { } ), Parameter( - testName = "DomainVerificationUserSelection", + testName = "DomainVerificationUserState", initial = { - DomainVerificationUserSelection( + DomainVerificationUserState( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", UserHandle.of(10), @@ -103,22 +102,22 @@ class DomainVerificationCoreApiTest { .associate { it.value to (it.index % 3) } ) }, - unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) }, + unparcel = { DomainVerificationUserState.CREATOR.createFromParcel(it) }, assertion = { first, second -> - assertAll<DomainVerificationUserSelection, UUID>(first, second, + assertAll<DomainVerificationUserState, UUID>(first, second, { it.identifier }, { it.component1() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, String>(first, second, + assertAll<DomainVerificationUserState, String>(first, second, { it.packageName }, { it.component2() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, UserHandle>(first, second, + assertAll<DomainVerificationUserState, UserHandle>(first, second, { it.user }, { it.component3() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, Boolean>( + assertAll<DomainVerificationUserState, Boolean>( first, second, { it.isLinkHandlingAllowed }, { it.component4() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, Map<String, Int>>( + assertAll<DomainVerificationUserState, Map<String, Int>>( first, second, { it.hostToStateMap }, { it.component5() }, IS_MAP_EQUAL_TO ) 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 89394837655a..53f0ca20e787 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 @@ -155,6 +155,15 @@ class DomainVerificationEnforcerTest { assertApprovedVerifier(it.callingUid, it.proxy) }, enforcer( + Type.SELECTION_QUERENT, + "approvedUserStateQuerent" + ) { + assertApprovedUserStateQuerent( + it.callingUid, it.callingUserId, + it.targetPackageName, it.userId + ) + }, + enforcer( Type.SELECTOR, "approvedUserSelector" ) { @@ -170,7 +179,7 @@ class DomainVerificationEnforcerTest { ArraySet(setOf("example.com")) ) }, - service(Type.INTERNAL, "setUserSelectionInternal") { + service(Type.INTERNAL, "setUserStateInternal") { setDomainVerificationUserSelectionInternal( it.userId, it.targetPackageName, @@ -184,11 +193,11 @@ class DomainVerificationEnforcerTest { service(Type.INTERNAL, "clearState") { clearDomainVerificationState(listOf(it.targetPackageName)) }, - service(Type.INTERNAL, "clearUserSelections") { - clearUserSelections(listOf(it.targetPackageName), it.userId) + service(Type.INTERNAL, "clearUserStates") { + clearUserStates(listOf(it.targetPackageName), it.userId) }, - service(Type.VERIFIER, "getPackageNames") { - validVerificationPackageNames + service(Type.VERIFIER, "queryValidPackageNames") { + queryValidVerificationPackageNames() }, service(Type.QUERENT, "getInfo") { getDomainVerificationInfo(it.targetPackageName) @@ -208,26 +217,13 @@ class DomainVerificationEnforcerTest { DomainVerificationManager.STATE_SUCCESS ) }, - service(Type.SELECTOR, "setLinkHandlingAllowed") { - setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true) - }, service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId) }, - service(Type.SELECTOR, "getUserSelection") { - getDomainVerificationUserSelection(it.targetPackageName) - }, - service(Type.SELECTOR_USER, "getUserSelectionUserId") { - getDomainVerificationUserSelection(it.targetPackageName, it.userId) + service(Type.SELECTION_QUERENT, "getUserStateUserId") { + getDomainVerificationUserState(it.targetPackageName, it.userId) }, - service(Type.SELECTOR, "setUserSelection") { - setDomainVerificationUserSelection( - it.targetDomainSetId, - setOf("example.com"), - true - ) - }, - service(Type.SELECTOR_USER, "setUserSelectionUserId") { + service(Type.SELECTOR_USER, "setUserStateUserId") { setDomainVerificationUserSelection( it.targetDomainSetId, setOf("example.com"), @@ -244,10 +240,6 @@ class DomainVerificationEnforcerTest { service(Type.LEGACY_QUERENT, "getLegacyUserState") { getLegacyState(it.targetPackageName, it.userId) }, - service(Type.OWNER_QUERENT, "getOwnersForDomain") { - // Re-use package name, since the result itself isn't relevant - getOwnersForDomain(it.targetPackageName) - }, service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") { // Re-use package name, since the result itself isn't relevant getOwnersForDomain(it.targetPackageName, it.userId) @@ -362,6 +354,7 @@ class DomainVerificationEnforcerTest { Type.INTERNAL -> internal() Type.QUERENT -> approvedQuerent() Type.VERIFIER -> approvedVerifier() + Type.SELECTION_QUERENT -> approvedUserStateQuerent(verifyCrossUser = true) Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false) Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true) Type.LEGACY_QUERENT -> legacyQuerent() @@ -371,7 +364,7 @@ class DomainVerificationEnforcerTest { }.run { /*exhaust*/ } } - fun internal() { + private fun internal() { val context: Context = mockThrowOnUnmocked() val target = params.construct(context) @@ -385,13 +378,13 @@ class DomainVerificationEnforcerTest { } } - fun approvedQuerent() { - val allowUserSelection = AtomicBoolean(false) + private fun approvedQuerent() { + val allowUserState = AtomicBoolean(false) val allowPreferredApps = AtomicBoolean(false) val allowQueryAll = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { initPermission( - allowUserSelection, + allowUserState, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION ) initPermission( @@ -418,7 +411,7 @@ class DomainVerificationEnforcerTest { assertFails { runMethod(target, NON_VERIFIER_UID) } - allowUserSelection.set(true) + allowUserState.set(true) assertFails { runMethod(target, NON_VERIFIER_UID) } @@ -427,7 +420,7 @@ class DomainVerificationEnforcerTest { runMethod(target, NON_VERIFIER_UID) } - fun approvedVerifier() { + private fun approvedVerifier() { val allowDomainVerificationAgent = AtomicBoolean(false) val allowIntentVerificationAgent = AtomicBoolean(false) val allowQueryAll = AtomicBoolean(false) @@ -469,12 +462,61 @@ class DomainVerificationEnforcerTest { assertFails { runMethod(target, NON_VERIFIER_UID) } } - fun approvedUserSelector(verifyCrossUser: Boolean) { - val allowUserSelection = AtomicBoolean(false) + private fun approvedUserStateQuerent(verifyCrossUser: Boolean) { + val allowInteractAcrossUsers = AtomicBoolean(false) + val context: Context = mockThrowOnUnmocked { + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + } + val target = params.construct(context) + + 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) + } + } + } + + val callingUserId = 0 + val notCallingUserId = 1 + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowInteractAcrossUsers.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = false) + } + } + + private fun approvedUserSelector(verifyCrossUser: Boolean) { + val allowUserState = AtomicBoolean(false) val allowInteractAcrossUsers = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { initPermission( - allowUserSelection, + allowUserState, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION ) initPermission( @@ -515,7 +557,7 @@ class DomainVerificationEnforcerTest { runTestCases(callingUserId, notCallingUserId, throws = true) } - allowUserSelection.set(true) + allowUserState.set(true) runTestCases(callingUserId, callingUserId, throws = false) if (verifyCrossUser) { @@ -641,7 +683,7 @@ class DomainVerificationEnforcerTest { private fun ownerQuerent(verifyCrossUser: Boolean) { val allowQueryAll = AtomicBoolean(false) - val allowUserSelection = AtomicBoolean(false) + val allowUserState = AtomicBoolean(false) val allowInteractAcrossUsers = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { initPermission( @@ -649,7 +691,7 @@ class DomainVerificationEnforcerTest { android.Manifest.permission.QUERY_ALL_PACKAGES ) initPermission( - allowUserSelection, + allowUserState, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION ) initPermission( @@ -690,7 +732,7 @@ class DomainVerificationEnforcerTest { runTestCases(callingUserId, notCallingUserId, throws = true) } - allowUserSelection.set(true) + allowUserState.set(true) runTestCases(callingUserId, callingUserId, throws = false) if (verifyCrossUser) { @@ -769,6 +811,9 @@ class DomainVerificationEnforcerTest { // INTERNAL || domain verification agent VERIFIER, + // No permissions, allows all apps to view domain state for visible packages + SELECTION_QUERENT, + // Holding the user setting permission SELECTOR, 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 439048ce51bb..8c31c65e1b0a 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 @@ -18,7 +18,7 @@ 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.DomainVerificationUserSelection +import android.content.pm.verify.domain.DomainVerificationUserState import com.android.server.pm.verify.domain.DomainVerificationPersistence operator fun <F> android.util.Pair<F, *>.component1() = first @@ -30,11 +30,11 @@ operator fun DomainVerificationInfo.component1() = identifier operator fun DomainVerificationInfo.component2() = packageName operator fun DomainVerificationInfo.component3() = hostToStateMap -operator fun DomainVerificationUserSelection.component1() = identifier -operator fun DomainVerificationUserSelection.component2() = packageName -operator fun DomainVerificationUserSelection.component3() = user -operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed -operator fun DomainVerificationUserSelection.component5() = hostToStateMap +operator fun DomainVerificationUserState.component1() = identifier +operator fun DomainVerificationUserState.component2() = packageName +operator fun DomainVerificationUserState.component3() = user +operator fun DomainVerificationUserState.component4() = isLinkHandlingAllowed +operator fun DomainVerificationUserState.component5() = hostToStateMap operator fun DomainVerificationPersistence.ReadResult.component1() = active operator fun DomainVerificationPersistence.ReadResult.component2() = restored 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 a92ab9e35ddc..6597577cf14f 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 @@ -22,14 +22,15 @@ import android.util.TypedXmlPullParser import android.util.TypedXmlSerializer import android.util.Xml import com.android.server.pm.verify.domain.DomainVerificationPersistence +import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState import com.android.server.pm.verify.domain.models.DomainVerificationPkgState import com.android.server.pm.verify.domain.models.DomainVerificationStateMap -import com.android.server.pm.verify.domain.models.DomainVerificationUserState import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import org.xmlpull.v1.XmlPullParser import java.io.File import java.nio.charset.StandardCharsets import java.util.UUID @@ -41,21 +42,41 @@ class DomainVerificationPersistenceTest { internal fun File.writeXml(block: (serializer: TypedXmlSerializer) -> Unit) = apply { outputStream().use { - // Explicitly use string based XML so it can printed in the test failure output - Xml.newFastSerializer() + // This must use the binary serializer the mirror the production behavior, as + // there are slight differences with the string based one. + Xml.newBinarySerializer() .apply { setOutput(it, StandardCharsets.UTF_8.name()) startDocument(null, true) setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true) + // Write a wrapping tag to ensure the domain verification settings didn't + // close out the document, allowing other settings to be written + startTag(null, "wrapper-tag") } .apply(block) + .apply { + startTag(null, "trailing-tag") + endTag(null, "trailing-tag") + endTag(null, "wrapper-tag") + } .endDocument() } } internal fun <T> File.readXml(block: (parser: TypedXmlPullParser) -> T) = inputStream().use { - block(Xml.resolvePullParser(it)) + val parser = Xml.resolvePullParser(it) + assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG) + assertThat(parser.name).isEqualTo("wrapper-tag") + assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG) + block(parser).also { + assertThat(parser.nextTag()).isEqualTo(XmlPullParser.START_TAG) + assertThat(parser.name).isEqualTo("trailing-tag") + assertThat(parser.nextTag()).isEqualTo(XmlPullParser.END_TAG) + assertThat(parser.name).isEqualTo("trailing-tag") + assertThat(parser.nextTag()).isEqualTo(XmlPullParser.END_TAG) + assertThat(parser.name).isEqualTo("wrapper-tag") + } } } @@ -102,14 +123,14 @@ class DomainVerificationPersistenceTest { // A domain without a written state falls back to default stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE - userSelectionStates[1] = DomainVerificationUserState(1).apply { + userStates[1] = DomainVerificationInternalUserState(1).apply { addHosts(setOf("example-user1.com", "example-user1.org")) isLinkHandlingAllowed = true } } val stateOne = mockEmptyPkgState(1).apply { // It's valid to have a user selection without any autoVerify domains - userSelectionStates[1] = DomainVerificationUserState(1).apply { + userStates[1] = DomainVerificationInternalUserState(1).apply { addHosts(setOf("example-user1.com", "example-user1.org")) isLinkHandlingAllowed = false } @@ -214,7 +235,7 @@ class DomainVerificationPersistenceTest { private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply { stateMap["$packageName.com"] = id - userSelectionStates[id] = DomainVerificationUserState(id).apply { + userStates[id] = DomainVerificationInternalUserState(id).apply { addHosts(setOf("$packageName-user.com")) isLinkHandlingAllowed = true } 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 db541f672954..91e5beccee09 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 @@ -122,7 +122,7 @@ class DomainVerificationProxyTest { whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt())) } collector = mockThrowOnUnmocked { - whenever(collectAutoVerifyDomains(any())) { + whenever(collectValidAutoVerifyDomains(any())) { when (val pkgName = (arguments[0] as AndroidPackage).packageName) { TEST_PKG_NAME_TARGET_ONE -> ArraySet(setOf("example1.com", "example2.com")) TEST_PKG_NAME_TARGET_TWO -> ArraySet(setOf("example3.com", "example4.com")) @@ -477,7 +477,7 @@ class DomainVerificationProxyTest { whenever( addPowerSaveTempWhitelistApp( anyInt(), anyString(), anyLong(), anyInt(), - anyBoolean(), anyString() + anyBoolean(), anyInt(), anyString() ) ) } 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 010eacf3f51f..0d8f275be09c 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 @@ -122,8 +122,8 @@ class DomainVerificationSettingsMutationTest { service("clearState") { clearDomainVerificationState(listOf(TEST_PKG)) }, - service("clearUserSelections") { - clearUserSelections(listOf(TEST_PKG), TEST_USER_ID) + service("clearUserStates") { + clearUserStates(listOf(TEST_PKG), TEST_USER_ID) }, service("setStatus") { setDomainVerificationStatus( @@ -147,19 +147,13 @@ class DomainVerificationSettingsMutationTest { DomainVerificationManager.STATE_SUCCESS ) }, - service("setLinkHandlingAllowed") { - setDomainVerificationLinkHandlingAllowed(TEST_PKG, true) - }, service("setLinkHandlingAllowedUserId") { setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, TEST_USER_ID) }, service("setLinkHandlingAllowedInternal") { setDomainVerificationLinkHandlingAllowedInternal(TEST_PKG, true, TEST_USER_ID) }, - service("setUserSelection") { - setDomainVerificationUserSelection(TEST_UUID, setOf("example.com"), true) - }, - service("setUserSelectionUserId") { + service("setUserStateUserId") { setDomainVerificationUserSelection( TEST_UUID, setOf("example.com"), @@ -167,7 +161,7 @@ class DomainVerificationSettingsMutationTest { TEST_USER_ID ) }, - service("setUserSelectionInternal") { + service("setUserStateInternal") { setDomainVerificationUserSelectionInternal( TEST_USER_ID, TEST_PKG, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 48056a2b54d1..0576125748fb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -16,18 +16,16 @@ package com.android.server.pm.test.verify.domain -import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.verify.domain.DomainVerificationManager -import android.content.pm.verify.domain.DomainVerificationUserSelection +import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Build import android.os.PatternMatcher import android.os.Process import android.util.ArraySet -import androidx.test.InstrumentationRegistry import com.android.server.pm.PackageSetting import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.verify.domain.DomainVerificationService @@ -41,7 +39,7 @@ import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString import java.util.UUID -class DomainVerificationManagerUserSelectionOverrideTest { +class DomainVerificationUserStateOverrideTest { companion object { private const val PKG_ONE = "com.test.one" @@ -50,17 +48,19 @@ class DomainVerificationManagerUserSelectionOverrideTest { private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") private val DOMAIN_ONE = - DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName + DomainVerificationUserStateOverrideTest::class.java.packageName - private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE - private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED - private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED + private const val STATE_NONE = DomainVerificationUserState.DOMAIN_STATE_NONE + private const val STATE_SELECTED = DomainVerificationUserState.DOMAIN_STATE_SELECTED + private const val STATE_VERIFIED = DomainVerificationUserState.DOMAIN_STATE_VERIFIED + + private const val USER_ID = 0 } private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE) private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO) - fun makeManager(): DomainVerificationManager = + fun makeService() = DomainVerificationService(mockThrowOnUnmocked { // Assume the test has every permission necessary whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) @@ -88,7 +88,7 @@ class DomainVerificationManagerUserSelectionOverrideTest { addPackage(pkg2) // Starting state for all tests is to have domain 1 enabled for the first package - setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true) + setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true, USER_ID) assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) } @@ -138,37 +138,37 @@ class DomainVerificationManagerUserSelectionOverrideTest { @Test fun anotherPackageTakeoverSuccess() { - val manager = makeManager() + val service = makeService() // Attempt override by package 2 - manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID) // 1 loses approval - assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE) + assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE) // 2 gains approval - assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) + assertThat(service.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) // 2 is the only owner - assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName }) .containsExactly(PKG_TWO) } @Test(expected = IllegalArgumentException::class) fun anotherPackageTakeoverFailure() { - val manager = makeManager() + val service = makeService() // Verify 1 to give it a higher approval level - manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), + service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), DomainVerificationManager.STATE_SUCCESS) - assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED) - assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + 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 - manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID) } - private fun DomainVerificationManager.stateFor(pkgName: String, host: String) = - getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host] + private fun DomainVerificationService.stateFor(pkgName: String, host: String) = + getDomainVerificationUserState(pkgName, USER_ID)!!.hostToStateMap[host] } 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 91098813380e..51c9b0ddb0d6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -85,6 +85,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.IAlarmCompleteListener; import android.app.IAlarmListener; @@ -1649,8 +1650,8 @@ public class AlarmManagerServiceTest { eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1669,8 +1670,8 @@ public class AlarmManagerServiceTest { eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1716,8 +1717,8 @@ public class AlarmManagerServiceTest { isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1742,8 +1743,8 @@ public class AlarmManagerServiceTest { eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1772,8 +1773,8 @@ public class AlarmManagerServiceTest { eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1797,8 +1798,8 @@ public class AlarmManagerServiceTest { eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @@ -1822,8 +1823,8 @@ public class AlarmManagerServiceTest { eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - final Bundle idleOptions = bundleCaptor.getValue(); - final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type); } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 18184b0a82af..f1d8e6c167d7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -592,6 +592,7 @@ public class LocalDisplayAdapterTest { new DisplayModeDirector.RefreshRateRange(60f, 60f), new DisplayModeDirector.RefreshRateRange(60f, 60f) )); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token, new SurfaceControl.DesiredDisplayModeSpecs( /* baseModeId */ 0, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index f7f592886473..3870b02ba37c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -641,6 +641,6 @@ public class ConnectivityControllerTest { private static JobStatus createJobStatus(JobInfo.Builder job, int uid, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { return new JobStatus(job.build(), uid, null, -1, 0, null, - earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0); + earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0, 0); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 91b3cb7dbdd9..7925b69852ba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -685,7 +685,7 @@ public class JobStatusTest { final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build(); return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis, - latestRunTimeElapsedMillis, 0, 0, null, 0); + latestRunTimeElapsedMillis, 0, 0, null, 0, 0); } private static JobStatus createJobStatus(JobInfo job) { diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java new file mode 100644 index 000000000000..d786a5dac83a --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usage; + +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mockitoSession; + +import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; +import android.content.Context; +import android.os.SystemClock; +import android.text.format.DateUtils; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.usage.UserUsageStatsService.StatsUpdatedListener; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.io.File; +import java.util.HashMap; + +@RunWith(AndroidJUnit4.class) +public class UserUsageStatsServiceTest { + private static final int TEST_USER_ID = 0; + private static final String TEST_PACKAGE_NAME = "test.package"; + private static final long TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS; + + private UserUsageStatsService mService; + private MockitoSession mMockitoSession; + + @Mock + private Context mContext; + @Mock + private StatsUpdatedListener mStatsUpdatedListener; + + @Before + public void setUp() { + mMockitoSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + + File dir = new File(InstrumentationRegistry.getContext().getCacheDir(), "test"); + mService = new UserUsageStatsService(mContext, TEST_USER_ID, dir, mStatsUpdatedListener); + + HashMap<String, Long> installedPkgs = new HashMap<>(); + installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis()); + + mService.init(System.currentTimeMillis(), installedPkgs); + } + + @After + public void tearDown() { + if (mMockitoSession != null) { + mMockitoSession.finishMocking(); + } + } + + @Test + public void testReportEvent_eventAppearsInQueries() { + Event event = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime()); + event.mPackage = TEST_PACKAGE_NAME; + mService.reportEvent(event); + + long now = System.currentTimeMillis(); + long startTime = now - TIME_INTERVAL_MILLIS; + UsageEvents events = mService.queryEventsForPackage( + startTime, now, TEST_PACKAGE_NAME, false /* includeTaskRoot */); + + boolean hasTestEvent = false; + while (events != null && events.hasNextEvent()) { + Event outEvent = new Event(); + events.getNextEvent(outEvent); + if (outEvent.mEventType == ACTIVITY_RESUMED) { + hasTestEvent = true; + } + } + assertTrue(hasTestEvent); + } + + @Test + public void testReportEvent_packageUsedEventNotTracked() { + Event event = new Event(APP_COMPONENT_USED, SystemClock.elapsedRealtime()); + event.mPackage = TEST_PACKAGE_NAME; + mService.reportEvent(event); + + long now = System.currentTimeMillis(); + long startTime = now - TIME_INTERVAL_MILLIS; + UsageEvents events = mService.queryEventsForPackage( + startTime, now, TEST_PACKAGE_NAME, false /* includeTaskRoot */); + + boolean hasTestEvent = false; + while (events != null && events.hasNextEvent()) { + Event outEvent = new Event(); + events.getNextEvent(outEvent); + if (outEvent.mEventType == APP_COMPONENT_USED) { + hasTestEvent = true; + } + } + assertFalse(hasTestEvent); + } +} diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java new file mode 100644 index 000000000000..489e2f769a3d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.test.AndroidTestCase; + +/** + * Tests for {@link com.android.server.BootReceiver} + */ +public class BootReceiverTest extends AndroidTestCase { + public void testLogLinePotentiallySensitive() throws Exception { + /* + * Strings to be dropped from the log as potentially sensitive: register dumps, process + * names, hardware info. + */ + final String[] becomeNull = { + "CPU: 4 PID: 120 Comm: kunit_try_catch Tainted: G W 5.8.0-rc6+ #7", + "Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1 04/01/2014", + "[ 0.083207] RSP: 0000:ffffffff8fe07ca8 EFLAGS: 00010046 ORIG_RAX: 0000000000000000", + "[ 0.084709] RAX: 0000000000000000 RBX: ffffffffff240000 RCX: ffffffff815fcf01", + "[ 0.086109] RDX: dffffc0000000000 RSI: 0000000000000001 RDI: ffffffffff240004", + "[ 0.087509] RBP: ffffffff8fe07d60 R08: fffffbfff1fc0f21 R09: fffffbfff1fc0f21", + "[ 0.088911] R10: ffffffff8fe07907 R11: fffffbfff1fc0f20 R12: ffffffff8fe07d38", + "R13: 0000000000000001 R14: 0000000000000001 R15: ffffffff8fe07e80", + "x29: ffff00003ce07150 x28: ffff80001aa29cc0", + "x1 : 0000000000000000 x0 : ffff00000f628000", + }; + + /* Strings to be left unchanged, including non-sensitive registers and parts of reports. */ + final String[] leftAsIs = { + "FS: 0000000000000000(0000) GS:ffffffff92409000(0000) knlGS:0000000000000000", + "[ 69.2366] [ T6006]c7 6006 =======================================================", + "[ 69.245688] [ T6006] BUG: KFENCE: out-of-bounds in kfence_handle_page_fault", + "[ 69.257816] [ T6006]c7 6006 Out-of-bounds access at 0xffffffca75c45000 ", + "[ 69.273536] [ T6006]c7 6006 __do_kernel_fault+0xa8/0x11c", + "pc : __mutex_lock+0x428/0x99c ", + "sp : ffff00003ce07150", + "Call trace:", + "", + }; + + final String[][] stripped = { + { "Detected corrupted memory at 0xffffffffb6797ff9 [ 0xac . . . . . . ]:", + "Detected corrupted memory at 0xffffffffb6797ff9" }, + }; + for (int i = 0; i < becomeNull.length; i++) { + assertEquals(BootReceiver.stripSensitiveData(becomeNull[i]), null); + } + + for (int i = 0; i < leftAsIs.length; i++) { + assertEquals(BootReceiver.stripSensitiveData(leftAsIs[i]), leftAsIs[i]); + } + + for (int i = 0; i < stripped.length; i++) { + assertEquals(BootReceiver.stripSensitiveData(stripped[i][0]), stripped[i][1]); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index cfa2086793a4..d6c11a549dfa 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -160,6 +160,7 @@ public class AbstractAccessibilityServiceConnectionTest { @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock private AccessibilityWindowManager mMockA11yWindowManager; @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock private AccessibilityTrace mMockA11yTrace; @Mock private WindowManagerInternal mMockWindowManagerInternal; @Mock private SystemActionPerformer mMockSystemActionPerformer; @Mock private IBinder mMockService; @@ -188,6 +189,7 @@ public class AbstractAccessibilityServiceConnectionTest { when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true); + when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); // Fake a11yWindowInfo and remote a11y connection for tests. addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false, Display.DEFAULT_DISPLAY); addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY); @@ -227,8 +229,8 @@ public class AbstractAccessibilityServiceConnectionTest { mServiceConnection = new TestAccessibilityServiceConnection(mMockContext, COMPONENT_NAME, mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy, - mMockSystemSupport, mMockWindowManagerInternal, mMockSystemActionPerformer, - mMockA11yWindowManager); + mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal, + mMockSystemActionPerformer, mMockA11yWindowManager); // Assume that the service is connected mServiceConnection.mService = mMockService; mServiceConnection.mServiceInterface = mMockServiceInterface; @@ -493,7 +495,8 @@ public class AbstractAccessibilityServiceConnectionTest { mServiceConnection.performAccessibilityAction(PIP_WINDOWID, ROOT_NODE_ID, ACTION_ACCESSIBILITY_FOCUS, null, INTERACTION_ID, mMockCallback, TID); - verify(mMockIPowerManager).userActivity(anyLong(), anyInt(), anyInt()); + verify(mMockIPowerManager).userActivity(eq(Display.DEFAULT_DISPLAY), anyLong(), anyInt(), + anyInt()); verify(mMockIA11yInteractionConnection).performAccessibilityAction(eq(ROOT_NODE_ID), eq(ACTION_ACCESSIBILITY_FOCUS), any(), eq(INTERACTION_ID), eq(mMockCallback), anyInt(), eq(PID), eq(TID)); @@ -849,12 +852,13 @@ public class AbstractAccessibilityServiceConnectionTest { TestAccessibilityServiceConnection(Context context, ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, - SystemSupport systemSupport, WindowManagerInternal windowManagerInternal, + SystemSupport systemSupport, AccessibilityTrace trace, + WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager a11yWindowManager) { super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock, - securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, - a11yWindowManager); + securityPolicy, systemSupport, trace, windowManagerInternal, + systemActionPerfomer, a11yWindowManager); mResolvedUserId = USER_ID; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java index 4b2a9fcd10d2..80e81d6e7cb9 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -30,7 +30,6 @@ import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEA import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -51,16 +50,19 @@ import android.view.MotionEvent; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; +import com.android.server.wm.WindowManagerInternal; 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; @@ -91,7 +93,9 @@ public class AccessibilityInputFilterTest { FullScreenMagnificationGestureHandler.class, TouchExplorer.class, AutoclickController.class, AccessibilityInputFilter.class}; - private FullScreenMagnificationController mMockFullScreenMagnificationController; + @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController; + @Mock private WindowManagerInternal mMockWindowManagerService; + @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; private AccessibilityManagerService mAms; private AccessibilityInputFilter mA11yInputFilter; private EventCaptor mCaptor1; @@ -134,16 +138,21 @@ public class AccessibilityInputFilterTest { public void setUp() { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getContext(); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.addService( + WindowManagerInternal.class, mMockWindowManagerService); + when(mMockWindowManagerService.getAccessibilityController()).thenReturn( + mMockA11yController); + when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false); setDisplayCount(1); mAms = spy(new AccessibilityManagerService(context)); - mMockFullScreenMagnificationController = mock(FullScreenMagnificationController.class); mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler); mA11yInputFilter.onInstalled(); - when(mAms.getValidDisplayList()).thenReturn(mDisplayList); - when(mAms.getFullScreenMagnificationController()).thenReturn( - mMockFullScreenMagnificationController); + doReturn(mDisplayList).when(mAms).getValidDisplayList(); + doReturn(mMockFullScreenMagnificationController).when(mAms) + .getFullScreenMagnificationController(); } @After diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java new file mode 100644 index 000000000000..170f561aa2da --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS; +import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.Instrumentation; +import android.content.Context; +import android.os.RemoteException; +import android.view.AccessibilityInteractionController; +import android.view.View; +import android.view.ViewRootImpl; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityNodeIdManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeProvider; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests that verify expected node and prefetched node results when finding a view by node id. We + * send some requests to the controller via View methods to control message timing. + */ +@RunWith(AndroidJUnit4.class) +public class AccessibilityInteractionControllerNodeRequestsTest { + private AccessibilityInteractionController mAccessibilityInteractionController; + @Mock + private IAccessibilityInteractionConnectionCallback mMockClientCallback1; + @Mock + private IAccessibilityInteractionConnectionCallback mMockClientCallback2; + + @Captor + private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor; + @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor; + + private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); + private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1; + private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2; + + private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout"; + private static final String TEXT_VIEW_1_DESCRIPTION = "textView1"; + private static final String TEXT_VIEW_2_DESCRIPTION = "textView2"; + + private TestFrameLayout mFrameLayout; + private TestTextView mTextView1; + private TestTextView2 mTextView2; + + private boolean mSendClient1RequestForTextAfterTextPrefetched; + private boolean mSendClient2RequestForTextAfterTextPrefetched; + private boolean mSendRequestForTextAndIncludeUnImportantViews; + private int mMockClient1InteractionId; + private int mMockClient2InteractionId; + + @Before + public void setUp() throws Throwable { + MockitoAnnotations.initMocks(this); + + mInstrumentation.runOnMainSync(() -> { + final Context context = mInstrumentation.getTargetContext(); + final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); + + mFrameLayout = new TestFrameLayout(context); + mTextView1 = new TestTextView(context); + mTextView2 = new TestTextView2(context); + + mFrameLayout.addView(mTextView1); + mFrameLayout.addView(mTextView2); + + // The controller retrieves views through this manager, and registration happens on + // when attached to a window, which we don't have. We can simply reference FrameLayout + // with ROOT_NODE_ID + AccessibilityNodeIdManager.getInstance().registerViewWithId( + mTextView1, mTextView1.getAccessibilityViewId()); + AccessibilityNodeIdManager.getInstance().registerViewWithId( + mTextView2, mTextView2.getAccessibilityViewId()); + + try { + viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null); + + } catch (WindowManager.BadTokenException e) { + // activity isn't running, we will ignore BadTokenException. + } + + mAccessibilityInteractionController = + new AccessibilityInteractionController(viewRootImpl); + }); + + } + + @After + public void tearDown() throws Throwable { + AccessibilityNodeIdManager.getInstance().unregisterViewWithId( + mTextView1.getAccessibilityViewId()); + AccessibilityNodeIdManager.getInstance().unregisterViewWithId( + mTextView2.getAccessibilityViewId()); + } + + /** + * Tests a basic request for the root node with prefetch flag + * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS} + * + * @throws RemoteException + */ + @Test + public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants() + throws RemoteException { + // Request for our FrameLayout + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + mInstrumentation.waitForIdleSync(); + + // Verify we get FrameLayout + verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); + AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); + + verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( + mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); + // The descendants are our two TextViews + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(2, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); + + } + + /** + * Tests a basic request for TestTextView1's node with prefetch flag + * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} + * + * @throws RemoteException + */ + @Test + public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings() + throws RemoteException { + // Request for TextView1 + sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId( + mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID), + mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); + mInstrumentation.waitForIdleSync(); + + // Verify we get TextView1 + verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); + AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); + + // Verify the prefetched sibling of TextView1 is TextView2 + verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( + mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); + // TextView2 is the prefetched sibling + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(1, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + } + + /** + * Tests a series of controller requests to prevent prefetching. + * Request 1: Client 1 requests the root node + * Request 2: When the root node is initialized in + * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}, + * Client 2 requests TestTextView1's node + * + * Request 2 on the queue prevents prefetching for Request 1. + * + * @throws RemoteException + */ + @Test + public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch() + throws RemoteException { + mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + final long nodeId = AccessibilityNodeInfo.makeNodeId( + mTextView1.getAccessibilityViewId(), + AccessibilityNodeProvider.HOST_VIEW_ID); + + // Enqueue a request when this node is found from a different service for + // TextView1 + sendNodeRequestToController(nodeId, mMockClientCallback2, + mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); + } + }); + // Client 1 request for FrameLayout + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + + mInstrumentation.waitForIdleSync(); + + // Verify client 1 gets FrameLayout + verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); + AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); + + // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo, + // meaning prefetching is interrupted and does not even begin for the first request + verify(mMockClientCallback1, never()) + .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); + + // Verify client 2 gets TextView1 + verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); + infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); + + // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS) + verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult( + mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId)); + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(1, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + } + + /** + * Tests a series of controller same-service requests to interrupt prefetching and satisfy a + * pending node request. + * Request 1: Request the root node + * Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching, + * request TestTextView1's node + * + * Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks + * if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for + * TestTextView1's node. Request 2 is fulfilled, so it is removed from queue and does not + * prefetch. + * + * @throws RemoteException + */ + @Test + public void testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg() + throws RemoteException { + mSendClient1RequestForTextAfterTextPrefetched = true; + + mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); + final long nodeId = AccessibilityNodeInfo.makeNodeId( + mTextView1.getAccessibilityViewId(), + AccessibilityNodeProvider.HOST_VIEW_ID); + + if (mSendClient1RequestForTextAfterTextPrefetched) { + // Prevent a loop when processing second request + mSendClient1RequestForTextAfterTextPrefetched = false; + // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a + // same-client request for TextView1 + sendNodeRequestToController(nodeId, mMockClientCallback1, + ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); + + } + } + }); + // Client 1 requests FrameLayout + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + + // Flush out all messages + mInstrumentation.waitForIdleSync(); + + // When TextView1 is prefetched for FrameLayout, we put a message on the queue in + // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get + // two node results for FrameLayout and TextView1. + verify(mMockClientCallback1, times(2)) + .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); + + List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues(); + assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription()); + + // The controller will look at FrameLayout's prefetched nodes and find matching nodes in + // pending requests. The prefetched TextView1 matches the second request. The second + // request was removed from queue and prefetching for this request never occurred. + verify(mMockClientCallback1, times(1)) + .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), + eq(mMockClient1InteractionId - 1)); + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(1, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + } + + /** + * Like above, but tests a series of controller requests from different services to interrupt + * prefetching and satisfy a pending node request. + * + * @throws RemoteException + */ + @Test + public void testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg() + throws RemoteException { + mSendClient2RequestForTextAfterTextPrefetched = true; + mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); + final long nodeId = AccessibilityNodeInfo.makeNodeId( + mTextView1.getAccessibilityViewId(), + AccessibilityNodeProvider.HOST_VIEW_ID); + + if (mSendClient2RequestForTextAfterTextPrefetched) { + mSendClient2RequestForTextAfterTextPrefetched = false; + // TextView1 is prefetched here. Now enqueue client 2's request for + // TextView1 + sendNodeRequestToController(nodeId, mMockClientCallback2, + mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); + } + } + }); + // Client 1 requests FrameLayout + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + + mInstrumentation.waitForIdleSync(); + + // Verify client 1 gets FrameLayout + verify(mMockClientCallback1, times(1)) + .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); + assertEquals(FRAME_LAYOUT_DESCRIPTION, + mFindInfoCaptor.getValue().getContentDescription()); + + // Verify client 1 has prefetched nodes + verify(mMockClientCallback1, times(1)) + .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), + eq(mMockClient1InteractionId)); + + // Verify client 1's only prefetched node is TextView1 + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(1, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + + // Verify client 2 gets TextView1 + verify(mMockClientCallback2, times(1)) + .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); + + assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription()); + + // The second request was removed from queue and prefetching for this client request never + // occurred as it was satisfied. + verify(mMockClientCallback2, never()) + .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); + + } + + @Test + public void testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest() + throws RemoteException { + mSendRequestForTextAndIncludeUnImportantViews = true; + mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); + final long nodeId = AccessibilityNodeInfo.makeNodeId( + mTextView1.getAccessibilityViewId(), + AccessibilityNodeProvider.HOST_VIEW_ID); + + if (mSendRequestForTextAndIncludeUnImportantViews) { + mSendRequestForTextAndIncludeUnImportantViews = false; + // TextView1 is prefetched here for client 1. Now enqueue a request from a + // different client that holds different fetch flags for TextView1 + sendNodeRequestToController(nodeId, mMockClientCallback2, + mMockClient2InteractionId, + FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS); + } + } + }); + + // Mockito does not make copies of objects when called. It holds references, so + // the captor would point to client 2's results after all requests are processed. Verify + // prefetched node immediately + doAnswer(invocation -> { + List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0); + assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription()); + return null; + }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(), + eq(mMockClient1InteractionId)); + + // Client 1 requests FrameLayout + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + + mInstrumentation.waitForIdleSync(); + + // Verify client 1 gets FrameLayout + verify(mMockClientCallback1, times(1)) + .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), + eq(mMockClient1InteractionId)); + + assertEquals(FRAME_LAYOUT_DESCRIPTION, + mFindInfoCaptor.getValue().getContentDescription()); + + // Verify client 1 has prefetched results. The only prefetched node is TextView1 + // (from above doAnswer) + verify(mMockClientCallback1, times(1)) + .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), + eq(mMockClient1InteractionId)); + + // Verify client 2 gets TextView1 + verify(mMockClientCallback2, times(1)) + .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), + eq(mMockClient2InteractionId)); + assertEquals(TEXT_VIEW_1_DESCRIPTION, + mFindInfoCaptor.getValue().getContentDescription()); + // Verify client 2 has TextView2 as a prefetched node + verify(mMockClientCallback2, times(1)) + .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), + eq(mMockClient2InteractionId)); + List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue(); + assertEquals(1, prefetchedNode.size()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription()); + } + + private void sendNodeRequestToController(long requestedNodeId, + IAccessibilityInteractionConnectionCallback callback, int interactionId, + int prefetchFlags) { + final int processAndThreadId = callback == mMockClientCallback1 + ? MOCK_CLIENT_1_THREAD_AND_PROCESS_ID + : MOCK_CLIENT_2_THREAD_AND_PROCESS_ID; + + mAccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdClientThread( + requestedNodeId, + null, interactionId, + callback, prefetchFlags, + processAndThreadId, + processAndThreadId, null, null); + + } + + private class TestFrameLayout extends FrameLayout { + + TestFrameLayout(Context context) { + super(context); + } + + @Override + public int getWindowVisibility() { + // We aren't attached to a window so let's pretend + return VISIBLE; + } + + @Override + public boolean isShown() { + // Controller check + return true; + } + + @Override + public int getAccessibilityViewId() { + // static id doesn't reset after tests so return the same one + return 0; + } + + @Override + public void addChildrenForAccessibility(ArrayList<View> outChildren) { + // ViewGroup#addChildrenForAccessbility sorting logic will switch these two + outChildren.add(mTextView1); + outChildren.add(mTextView2); + } + + @Override + public boolean includeForAccessibility() { + return true; + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setContentDescription(FRAME_LAYOUT_DESCRIPTION); + } + } + + private class TestTextView extends TextView { + TestTextView(Context context) { + super(context); + } + + @Override + public int getWindowVisibility() { + return VISIBLE; + } + + @Override + public boolean isShown() { + return true; + } + + @Override + public int getAccessibilityViewId() { + return 1; + } + + @Override + public boolean includeForAccessibility() { + return true; + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); + } + } + + private class TestTextView2 extends TextView { + TestTextView2(Context context) { + super(context); + } + + @Override + public int getWindowVisibility() { + return VISIBLE; + } + + @Override + public boolean isShown() { + return true; + } + + @Override + public int getAccessibilityViewId() { + return 2; + } + + @Override + public boolean includeForAccessibility() { + return true; + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setContentDescription(TEXT_VIEW_2_DESCRIPTION); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 110bb21b5851..bcc756a0f8e8 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -84,6 +84,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { @Mock private AccessibilityServiceInfo mMockServiceInfo; @Mock private ResolveInfo mMockResolveInfo; @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController; @Mock private PackageManager mMockPackageManager; @Mock private WindowManagerInternal mMockWindowManagerService; @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; @@ -115,6 +116,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( mMockWindowMagnificationMgr); + when(mMockWindowManagerService.getAccessibilityController()).thenReturn( + mMockA11yController); + when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false); mA11yms = new AccessibilityManagerService( InstrumentationRegistry.getContext(), mMockPackageManager, @@ -153,6 +157,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { new Object(), mMockSecurityPolicy, mMockSystemSupport, + mA11yms, mMockWindowManagerService, mMockSystemActionPerformer, mMockA11yWindowManager, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index 6963a1ab1538..00daa5c89fba 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -85,6 +85,7 @@ public class AccessibilityServiceConnectionTest { @Mock AccessibilityWindowManager mMockA11yWindowManager; @Mock ActivityTaskManagerInternal mMockActivityTaskManagerInternal; @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock AccessibilityTrace mMockA11yTrace; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock SystemActionPerformer mMockSystemActionPerformer; @Mock KeyEventDispatcher mMockKeyEventDispatcher; @@ -110,12 +111,13 @@ public class AccessibilityServiceConnectionTest { mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockIBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); + when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext, COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(), - mMockSecurityPolicy, mMockSystemSupport, mMockWindowManagerInternal, - mMockSystemActionPerformer, mMockA11yWindowManager, - mMockActivityTaskManagerInternal); + mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace, + mMockWindowManagerInternal, mMockSystemActionPerformer, + mMockA11yWindowManager, mMockActivityTaskManagerInternal); when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java index 85b8fcbbcc61..ebb73e877db4 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java @@ -39,6 +39,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; +import android.view.Display; import android.view.KeyEvent; import androidx.test.InstrumentationRegistry; @@ -172,8 +173,8 @@ public class KeyEventDispatcherTest { mFilter1SequenceCaptor.getValue()); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verify(mMockPowerManagerService, times(1)).userActivity(anyLong(), - eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); + verify(mMockPowerManagerService, times(1)).userActivity(eq(Display.DEFAULT_DISPLAY), + anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); assertFalse(isTimeoutPending(mMessageCapturingHandler)); } @@ -204,8 +205,8 @@ public class KeyEventDispatcherTest { mFilter2SequenceCaptor.getValue()); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verify(mMockPowerManagerService, times(1)).userActivity(anyLong(), - eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); + verify(mMockPowerManagerService, times(1)).userActivity(eq(Display.DEFAULT_DISPLAY), + anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); assertFalse(isTimeoutPending(mMessageCapturingHandler)); } @@ -221,8 +222,8 @@ public class KeyEventDispatcherTest { mFilter2SequenceCaptor.getValue()); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verify(mMockPowerManagerService, times(1)).userActivity(anyLong(), - eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); + verify(mMockPowerManagerService, times(1)).userActivity(eq(Display.DEFAULT_DISPLAY), + anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); assertFalse(isTimeoutPending(mMessageCapturingHandler)); } @@ -238,8 +239,8 @@ public class KeyEventDispatcherTest { mFilter2SequenceCaptor.getValue()); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verify(mMockPowerManagerService, times(1)).userActivity(anyLong(), - eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); + verify(mMockPowerManagerService, times(1)).userActivity(eq(Display.DEFAULT_DISPLAY), + anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); assertFalse(isTimeoutPending(mMessageCapturingHandler)); } @@ -308,8 +309,8 @@ public class KeyEventDispatcherTest { mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0)); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verify(mMockPowerManagerService, times(1)).userActivity(anyLong(), - eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); + verify(mMockPowerManagerService, times(1)).userActivity(eq(Display.DEFAULT_DISPLAY), + anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); } @Test @@ -357,8 +358,8 @@ public class KeyEventDispatcherTest { mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0)); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verify(mMockPowerManagerService, times(1)).userActivity(anyLong(), - eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); + verify(mMockPowerManagerService, times(1)).userActivity(eq(Display.DEFAULT_DISPLAY), + anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY), eq(0)); } /* diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 8062bfec3703..160308762a58 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -63,6 +63,7 @@ public class UiAutomationManagerTest { @Mock AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock AccessibilityWindowManager mMockA11yWindowManager; @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; + @Mock AccessibilityTrace mMockA11yTrace; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock SystemActionPerformer mMockSystemActionPerformer; @Mock IBinder mMockOwner; @@ -80,6 +81,7 @@ public class UiAutomationManagerTest { mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockAccessibilityServiceClient.asBinder()).thenReturn(mMockServiceAsBinder); + when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); final Context context = getInstrumentation().getTargetContext(); when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn( @@ -197,7 +199,7 @@ public class UiAutomationManagerTest { private void register(int flags) { mUiAutomationManager.registerUiTestAutomationServiceLocked(mMockOwner, mMockAccessibilityServiceClient, mMockContext, mMockServiceInfo, SERVICE_ID, - mMessageCapturingHandler, mMockSecurityPolicy, mMockSystemSupport, + mMessageCapturingHandler, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal, mMockSystemActionPerformer, mMockA11yWindowManager, flags); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java index 895fb1757504..5dbf837b08b2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java @@ -69,7 +69,7 @@ public class MagnificationGesturesObserverTest { mContext = InstrumentationRegistry.getContext(); mInstrumentation = InstrumentationRegistry.getInstrumentation(); mObserver = new MagnificationGesturesObserver(mCallback, new SimpleSwipe(mContext), - new TwoFingersDown(mContext)); + new TwoFingersDownOrSwipe(mContext)); } @Test @@ -77,9 +77,7 @@ public class MagnificationGesturesObserverTest { final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY, DEFAULT_X , DEFAULT_Y); - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(moveEvent, moveEvent, 0); - }); + mObserver.onMotionEvent(moveEvent, moveEvent, 0); verify(mCallback).onGestureCancelled(eq(0L), mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(moveEvent))); @@ -92,9 +90,7 @@ public class MagnificationGesturesObserverTest { final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, DEFAULT_X , DEFAULT_Y); - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(downEvent, downEvent, 0); - }); + mObserver.onMotionEvent(downEvent, downEvent, 0); verify(mCallback).onGestureCancelled(eq(0L), mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent))); @@ -108,9 +104,7 @@ public class MagnificationGesturesObserverTest { final int timeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( mContext) + 100; - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(downEvent, downEvent, 0); - }); + mObserver.onMotionEvent(downEvent, downEvent, 0); verify(mCallback, timeout(timeoutMillis)).onGestureCancelled(eq(downEvent.getDownTime()), mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent))); @@ -121,14 +115,12 @@ public class MagnificationGesturesObserverTest { public void sendEventsOfSwiping_onGestureCompleted() { final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, DEFAULT_X, DEFAULT_Y); - final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY, DEFAULT_X + swipeDistance, DEFAULT_Y + swipeDistance); - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(downEvent, downEvent, 0); - mObserver.onMotionEvent(moveEvent, moveEvent, 0); - }); + mObserver.onMotionEvent(downEvent, downEvent, 0); + mObserver.onMotionEvent(moveEvent, moveEvent, 0); verify(mCallback).onGestureCompleted(eq(MagnificationGestureMatcher.GESTURE_SWIPE), eq(downEvent.getDownTime()), mEventInfoArgumentCaptor.capture(), diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java index 01631bf21a63..0ca631e4ed62 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java @@ -78,7 +78,7 @@ public class SimpleSwipeTest { @Test public void sendSwipeEvent_onGestureCompleted() { - final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, DEFAULT_X, DEFAULT_Y); final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java index ed8dc4e470de..162d2a9d98af 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.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. @@ -16,14 +16,20 @@ package com.android.server.accessibility.magnification; +import static com.android.server.accessibility.utils.TouchEventGenerator.movePointer; +import static com.android.server.accessibility.utils.TouchEventGenerator.twoPointersDownEvents; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.after; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import android.content.Context; +import android.graphics.PointF; import android.view.Display; import android.view.MotionEvent; +import android.view.ViewConfiguration; import androidx.test.InstrumentationRegistry; @@ -35,18 +41,20 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + /** - * Tests for {@link TwoFingersDown}. + * Tests for {@link TwoFingersDownOrSwipe}. */ -public class TwoFingersDownTest { +public class TwoFingersDownOrSwipeTest { private static final float DEFAULT_X = 100f; private static final float DEFAULT_Y = 100f; - private static Context sContext; + private static float sSwipeMinDistance; private static int sTimeoutMillis; + private static Context sContext; - private Context mContext; private GesturesObserver mGesturesObserver; @Mock private GesturesObserver.Listener mListener; @@ -56,13 +64,13 @@ public class TwoFingersDownTest { sContext = InstrumentationRegistry.getContext(); sTimeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( sContext) + 100; + sSwipeMinDistance = ViewConfiguration.get(sContext).getScaledTouchSlop() + 1; } @Before public void setUp() { - mContext = InstrumentationRegistry.getContext(); MockitoAnnotations.initMocks(this); - mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDown(mContext)); + mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDownOrSwipe(sContext)); } @Test @@ -78,24 +86,16 @@ public class TwoFingersDownTest { @Test public void sendTwoFingerDownEvent_onGestureCompleted() { - final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, - DEFAULT_X, DEFAULT_Y); - final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); - defPointerCoords.x = DEFAULT_X; - defPointerCoords.y = DEFAULT_Y; - final MotionEvent.PointerCoords secondPointerCoords = new MotionEvent.PointerCoords(); - secondPointerCoords.x = DEFAULT_X + 10; - secondPointerCoords.y = DEFAULT_Y + 10; + final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, + new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); - final MotionEvent twoPointersDownEvent = TouchEventGenerator.twoPointersDownEvent( - Display.DEFAULT_DISPLAY, defPointerCoords, secondPointerCoords); - - mGesturesObserver.onMotionEvent(downEvent, downEvent, 0); - mGesturesObserver.onMotionEvent(twoPointersDownEvent, twoPointersDownEvent, 0); + for (MotionEvent event : downEvents) { + mGesturesObserver.onMotionEvent(event, event, 0); + } verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted( - MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, twoPointersDownEvent, - twoPointersDownEvent, 0); + MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, downEvents.get(1), + downEvents.get(1), 0); } @Test @@ -108,7 +108,39 @@ public class TwoFingersDownTest { mGesturesObserver.onMotionEvent(downEvent, downEvent, 0); mGesturesObserver.onMotionEvent(upEvent, upEvent, 0); - verify(mListener, timeout(sTimeoutMillis)).onGestureCancelled(any(MotionEvent.class), - any(MotionEvent.class), eq(0)); + verify(mListener, after(ViewConfiguration.getDoubleTapTimeout())).onGestureCancelled( + any(MotionEvent.class), any(MotionEvent.class), eq(0)); + } + + @Test + public void firstPointerMove_twoPointersDown_onGestureCompleted() { + final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, + new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); + for (MotionEvent event : downEvents) { + mGesturesObserver.onMotionEvent(event, event, 0); + } + final MotionEvent moveEvent = movePointer(downEvents.get(1), 0, sSwipeMinDistance, 0); + + mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0); + + verify(mListener).onGestureCompleted( + MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent, + moveEvent, 0); + } + + @Test + public void secondPointerMove_twoPointersDown_onGestureCompleted() { + final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, + new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); + for (MotionEvent event : downEvents) { + mGesturesObserver.onMotionEvent(event, event, 0); + } + final MotionEvent moveEvent = movePointer(downEvents.get(1), 1, sSwipeMinDistance, 0); + + mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0); + + verify(mListener).onGestureCompleted( + MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent, + moveEvent, 0); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index 4b7ebbc29b46..b9498d641ed7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -24,13 +24,15 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; +import android.graphics.PointF; import android.graphics.Rect; import android.os.RemoteException; import android.util.DebugUtils; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.ViewConfiguration; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.EventStreamTransformation; @@ -43,6 +45,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.function.IntConsumer; /** @@ -75,7 +78,7 @@ public class WindowMagnificationGestureHandlerTest { @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getContext(); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0, mock(WindowMagnificationManager.Callback.class)); mMockConnection = new MockWindowMagnificationConnection(); @@ -100,8 +103,8 @@ public class WindowMagnificationGestureHandlerTest { * Covers following paths to get to and back between each state and {@link #STATE_IDLE}. * <p> * <br> IDLE -> SHOW_MAGNIFIER [label="a11y\nbtn"] - * <br> SHOW_MAGNIFIER -> TWO_FINGER_DOWN [label="2hold"] - * <br> TWO_FINGER_DOWN -> SHOW_MAGNIFIER [label="release"] + * <br> SHOW_MAGNIFIER -> TWO_FINGERS_DOWN [label="2hold"] + * <br> TWO_FINGERS_DOWN -> SHOW_MAGNIFIER [label="release"] * <br> SHOW_MAGNIFIER -> IDLE [label="a11y\nbtn"] * <br> IDLE -> SHOW_MAGNIFIER_TRIPLE_TAP [label="3tap"] * <br> SHOW_MAGNIFIER_TRIPLE_TAP -> IDLE [label="3tap"] @@ -112,18 +115,16 @@ public class WindowMagnificationGestureHandlerTest { */ @Test public void testEachState_isReachableAndRecoverable() { - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - forEachState(state -> { - goFromStateIdleTo(state); - assertIn(state); - returnToNormalFrom(state); - try { - assertIn(STATE_IDLE); - } catch (AssertionError e) { - throw new AssertionError("Failed while testing state " + stateToString(state), - e); - } - }); + forEachState(state -> { + goFromStateIdleTo(state); + assertIn(state); + returnToNormalFrom(state); + try { + assertIn(STATE_IDLE); + } catch (AssertionError e) { + throw new AssertionError("Failed while testing state " + stateToString(state), + e); + } }); } @@ -209,10 +210,19 @@ public class WindowMagnificationGestureHandlerTest { case STATE_TWO_FINGERS_DOWN: { goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT); final Rect frame = mMockConnection.getMirrorWindowFrame(); - send(downEvent(frame.centerX(), frame.centerY())); - //Second finger is outside the window. - send(twoPointerDownEvent(new float[]{frame.centerX(), frame.centerX() + 10}, - new float[]{frame.centerY(), frame.centerY() + 10})); + final PointF firstPointerDown = new PointF(frame.centerX(), frame.centerY()); + // The second finger is outside the window. + final PointF secondPointerDown = new PointF(frame.right + 10, + frame.bottom + 10); + final List<MotionEvent> motionEvents = + TouchEventGenerator.twoPointersDownEvents(DISPLAY_0, + firstPointerDown, secondPointerDown); + for (MotionEvent downEvent: motionEvents) { + send(downEvent); + } + // Wait for two-finger down gesture completed. + Thread.sleep(ViewConfiguration.getDoubleTapTimeout()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } break; case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: { @@ -301,16 +311,6 @@ public class WindowMagnificationGestureHandlerTest { send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); } - private MotionEvent twoPointerDownEvent(float[] x, float[] y) { - final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); - defPointerCoords.x = x[0]; - defPointerCoords.y = y[0]; - final MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords(); - pointerCoords.x = x[1]; - pointerCoords.y = y[1]; - return TouchEventGenerator.twoPointersDownEvent(DISPLAY_0, defPointerCoords, pointerCoords); - } - private String stateDump() { return "\nCurrent state dump:\n" + mWindowMagnificationGestureHandler.mCurrentState; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java index a05881f78892..fbcde533aa9f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java @@ -19,16 +19,19 @@ package com.android.server.accessibility.utils; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_DOWN; +import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; -import static android.view.MotionEvent.PointerCoords; +import android.graphics.PointF; import android.os.SystemClock; import android.view.InputDevice; import android.view.MotionEvent; +import java.util.ArrayList; +import java.util.List; + /** * generates {@link MotionEvent} with source {@link InputDevice#SOURCE_TOUCHSCREEN} - * */ public class TouchEventGenerator { @@ -39,44 +42,68 @@ public class TouchEventGenerator { public static MotionEvent moveEvent(int displayId, float x, float y) { return generateSingleTouchEvent(displayId, ACTION_MOVE, x, y); } + public static MotionEvent upEvent(int displayId, float x, float y) { return generateSingleTouchEvent(displayId, ACTION_UP, x, y); } - public static MotionEvent twoPointersDownEvent(int displayId, PointerCoords defPointerCoords, - PointerCoords pointerCoords) { - return generateTwoPointersEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords, - pointerCoords); - } - private static MotionEvent generateSingleTouchEvent(int displayId, int action, float x, float y) { - final long downTime = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(downTime, downTime, - action, x, y, 0); - ev.setDisplayId(displayId); - ev.setSource(InputDevice.SOURCE_TOUCHSCREEN); - return ev; + return generateMultiplePointersEvent(displayId, action, new PointF(x, y)); } - private static MotionEvent generateTwoPointersEvent(int displayId, int action, - PointerCoords defPointerCoords, PointerCoords pointerCoords) { - final long downTime = SystemClock.uptimeMillis(); - MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties(); - defPointerProperties.id = 0; - defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; - MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties(); - pointerProperties.id = 1; - pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; + /** + * Creates a list of {@link MotionEvent} with given pointers location. + * + * @param displayId the display id + * @param pointF1 location on the screen of the second pointer. + * @param pointF2 location on the screen of the second pointer. + * @return a list of {@link MotionEvent} with {@link MotionEvent#ACTION_DOWN} and {@link + * MotionEvent#ACTION_POINTER_DOWN}. + */ + public static List<MotionEvent> twoPointersDownEvents(int displayId, PointF pointF1, + PointF pointF2) { + final List<MotionEvent> downEvents = new ArrayList<>(); + final MotionEvent downEvent = generateMultiplePointersEvent(displayId, + MotionEvent.ACTION_DOWN, pointF1); + downEvents.add(downEvent); + final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int action = ACTION_POINTER_DOWN | actionIndex; + + final MotionEvent twoPointersDownEvent = generateMultiplePointersEvent(displayId, action, + pointF1, pointF2); + downEvents.add(twoPointersDownEvent); + return downEvents; + } + + private static MotionEvent generateMultiplePointersEvent(int displayId, int action, + PointF... pointFs) { + final int length = pointFs.length; + final MotionEvent.PointerCoords[] pointerCoordsArray = + new MotionEvent.PointerCoords[length]; + final MotionEvent.PointerProperties[] pointerPropertiesArray = + new MotionEvent.PointerProperties[length]; + for (int i = 0; i < length; i++) { + MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords(); + pointerCoords.x = pointFs[i].x; + pointerCoords.y = pointFs[i].y; + pointerCoordsArray[i] = pointerCoords; + + MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties(); + pointerProperties.id = i; + pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; + pointerPropertiesArray[i] = pointerProperties; + } + + final long downTime = SystemClock.uptimeMillis(); final MotionEvent ev = MotionEvent.obtain( /* downTime */ downTime, /* eventTime */ downTime, /* action */ action, - /* pointerCount */ 2, - /* pointerProperties */ new MotionEvent.PointerProperties[] { - defPointerProperties, pointerProperties}, - /* pointerCoords */ new PointerCoords[] { defPointerCoords, pointerCoords }, + /* pointerCount */ length, + /* pointerProperties */ pointerPropertiesArray, + /* pointerCoords */ pointerCoordsArray, /* metaState */ 0, /* buttonState */ 0, /* xPrecision */ 1.0f, @@ -88,4 +115,65 @@ public class TouchEventGenerator { ev.setDisplayId(displayId); return ev; } + + /** + * Generates a move event that moves the pointer of the original event with given index. + * The original event should not be up event and we don't support + * {@link MotionEvent#ACTION_POINTER_UP} now. + * + * @param originalEvent the move or down event + * @param pointerIndex the index of the pointer we want to move. + * @param offsetX the offset in X coordinate. + * @param offsetY the offset in Y coordinate. + * @return a motion event with move action. + */ + public static MotionEvent movePointer(MotionEvent originalEvent, int pointerIndex, + float offsetX, float offsetY) { + if (originalEvent.getActionMasked() == ACTION_UP) { + throw new IllegalArgumentException("No pointer is on the screen"); + } + + if (originalEvent.getActionMasked() == ACTION_POINTER_UP) { + throw new IllegalArgumentException("unsupported yet,please implement it first"); + } + + final int pointerCount = originalEvent.getPointerCount(); + if (pointerIndex >= pointerCount) { + throw new IllegalArgumentException( + pointerIndex + "is not available with pointer count" + pointerCount); + } + final int action = MotionEvent.ACTION_MOVE; + final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[pointerCount]; + for (int i = 0; i < pointerCount; i++) { + MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties(); + originalEvent.getPointerProperties(i, pointerProperty); + pp[i] = pointerProperty; + } + + final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[pointerCount]; + for (int i = 0; i < pointerCount; i++) { + MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords(); + originalEvent.getPointerCoords(i, pointerCoord); + pc[i] = pointerCoord; + } + pc[pointerIndex].x += offsetX; + pc[pointerIndex].y += offsetY; + final MotionEvent ev = MotionEvent.obtain( + /* downTime */ originalEvent.getDownTime(), + /* eventTime */ SystemClock.uptimeMillis(), + /* action */ action, + /* pointerCount */ 2, + /* pointerProperties */ pp, + /* pointerCoords */ pc, + /* metaState */ 0, + /* buttonState */ 0, + /* xPrecision */ 1.0f, + /* yPrecision */ 1.0f, + /* deviceId */ 0, + /* edgeFlags */ 0, + /* source */ originalEvent.getSource(), + /* flags */ originalEvent.getFlags()); + ev.setDisplayId(originalEvent.getDisplayId()); + return ev; + } } 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 97daea3011ea..0b1c120d8a1e 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 @@ -67,7 +67,6 @@ public class SnippetTest { .setPropertyName(propertyKeyString) .addSnippetMatches( SnippetMatchProto.newBuilder() - .setValuesIndex(0) .setExactMatchPosition(29) .setExactMatchBytes(3) .setWindowPosition(26) @@ -174,7 +173,6 @@ public class SnippetTest { .setPropertyName("sender.name") .addSnippetMatches( SnippetMatchProto.newBuilder() - .setValuesIndex(0) .setExactMatchPosition(0) .setExactMatchBytes(4) .setWindowPosition(0) @@ -186,7 +184,6 @@ public class SnippetTest { .setPropertyName("sender.email") .addSnippetMatches( SnippetMatchProto.newBuilder() - .setValuesIndex(0) .setExactMatchPosition(0) .setExactMatchBytes(20) .setWindowPosition(0) diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index 8b0e948579fb..b6b6932c4a93 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -602,12 +602,12 @@ public class CompatConfigTest { .addEnableSinceSdkChangeWithId(2, 2L) .build(); compatConfig.forceNonDebuggableFinalForTest(true); - compatConfig.initOverrides(overridesFile); + compatConfig.initOverrides(overridesFile, new File("")); when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) .thenReturn(ApplicationInfoBuilder.create() - .withPackageName("foo.bar") - .debuggable() - .build()); + .withPackageName("foo.bar") + .debuggable() + .build()); when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) .thenThrow(new NameNotFoundException()); @@ -649,7 +649,7 @@ public class CompatConfigTest { .addEnableSinceSdkChangeWithId(2, 2L) .build(); compatConfig.forceNonDebuggableFinalForTest(true); - compatConfig.initOverrides(overridesFile); + compatConfig.initOverrides(overridesFile, new File("")); compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L, new PackageOverride.Builder() @@ -673,11 +673,11 @@ public class CompatConfigTest { } @Test - public void testLoadOverridesRaw() throws Exception { + public void testInitOverridesRaw() throws Exception { File tempDir = createTempDir(); File overridesFile = new File(tempDir, "overrides.xml"); // Change 1 is enabled for foo.bar (validated) - // Change 2 is disabled for bar.baz (deferred) + // Change 2 is disabled for bar.baz (raw) String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<overrides>\n" + " <change-overrides changeId=\"1\">\n" @@ -709,7 +709,7 @@ public class CompatConfigTest { .addEnableSinceSdkChangeWithId(2, 2L) .build(); compatConfig.forceNonDebuggableFinalForTest(true); - compatConfig.initOverrides(overridesFile); + compatConfig.initOverrides(overridesFile, new File("")); ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("foo.bar") .withVersionCode(100L) @@ -728,7 +728,7 @@ public class CompatConfigTest { } @Test - public void testLoadOverridesDeferred() throws Exception { + public void testInitOverridesDeferred() throws Exception { File tempDir = createTempDir(); File overridesFile = new File(tempDir, "overrides.xml"); // Change 1 is enabled for foo.bar (validated) @@ -754,7 +754,7 @@ public class CompatConfigTest { .addEnableSinceSdkChangeWithId(2, 2L) .build(); compatConfig.forceNonDebuggableFinalForTest(true); - compatConfig.initOverrides(overridesFile); + compatConfig.initOverrides(overridesFile, new File("")); ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("foo.bar") .debuggable() @@ -767,4 +767,115 @@ public class CompatConfigTest { assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue(); assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse(); } + + @Test + public void testInitOverridesWithStaticFile() throws Exception { + File tempDir = createTempDir(); + File dynamicOverridesFile = new File(tempDir, "dynamic_overrides.xml"); + File staticOverridesFile = new File(tempDir, "static_overrides.xml"); + // Change 1 is enabled for foo.bar (raw) + // Change 2 is disabled for bar.baz (raw) + String dynamicXmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<overrides>" + + "<change-overrides changeId=\"1\">" + + "<raw>" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + "</raw>" + + "</change-overrides>" + + "<change-overrides changeId=\"2\">" + + "<raw>" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + "</raw>" + + "</change-overrides>" + + "</overrides>"; + writeToFile(tempDir, "dynamic_overrides.xml", dynamicXmlData); + // Change 2 is enabled for foo.bar and bar.baz (raw) + // Change 3 is enabled for bar.baz (raw) + String staticXmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<overrides>" + + "<change-overrides changeId=\"2\">" + + "<raw>" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + "</raw>" + + "</change-overrides>" + + "<change-overrides changeId=\"3\">" + + "<raw>" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + "</raw>" + + "</change-overrides>" + + "</overrides>"; + writeToFile(tempDir, "static_overrides.xml", staticXmlData); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1L) + .addDisabledChangeWithId(2L) + .addDisabledChangeWithId(3L) + .build(); + compatConfig.forceNonDebuggableFinalForTest(true); + // Adding an override that will be cleared after initOverrides is called. + compatConfig.addOverride(1L, "bar.baz", true); + compatConfig.initOverrides(dynamicOverridesFile, staticOverridesFile); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenThrow(new NameNotFoundException()); + when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) + .thenThrow(new NameNotFoundException()); + + assertThat(compatConfig.willChangeBeEnabled(1L, "foo.bar")).isTrue(); + assertThat(compatConfig.willChangeBeEnabled(2L, "foo.bar")).isTrue(); + assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse(); + assertThat(compatConfig.willChangeBeEnabled(3L, "bar.baz")).isTrue(); + assertThat(readFile(dynamicOverridesFile)) + .isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + " <change-overrides changeId=\"2\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + " <change-overrides changeId=\"3\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + "</overrides>\n"); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 26a549d77664..a97ea268b1c8 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -274,6 +274,87 @@ public final class DeviceStateManagerServiceTest { } @Test + public void requestState_pendingStateAtRequest() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + mPolicy.blockConfigure(); + + final IBinder firstRequestToken = new Binder(); + final IBinder secondRequestToken = new Binder(); + assertEquals(callback.getLastNotifiedStatus(firstRequestToken), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + assertEquals(callback.getLastNotifiedStatus(secondRequestToken), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestState(firstRequestToken, + OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); + + assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE); + assertEquals(mService.getBaseState(), DEFAULT_DEVICE_STATE); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + mService.getBinderService().requestState(secondRequestToken, + DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */); + + mPolicy.resumeConfigureOnce(); + + // First request status is now suspended as there is another pending request. + assertEquals(callback.getLastNotifiedStatus(firstRequestToken), + TestDeviceStateManagerCallback.STATUS_SUSPENDED); + // Second request status still unknown because the service is still awaiting policy + // callback. + assertEquals(callback.getLastNotifiedStatus(secondRequestToken), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); + assertEquals(mService.getPendingState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState(), DEFAULT_DEVICE_STATE); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + DEFAULT_DEVICE_STATE.getIdentifier()); + + mPolicy.resumeConfigure(); + + assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getPendingState(), Optional.empty()); + assertEquals(mService.getBaseState(), DEFAULT_DEVICE_STATE); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + DEFAULT_DEVICE_STATE.getIdentifier()); + + // Now cancel the second request to make the first request active. + mService.getBinderService().cancelRequest(secondRequestToken); + + assertEquals(callback.getLastNotifiedStatus(firstRequestToken), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + assertEquals(callback.getLastNotifiedStatus(secondRequestToken), + TestDeviceStateManagerCallback.STATUS_CANCELED); + + assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); + assertEquals(mService.getPendingState(), Optional.empty()); + assertEquals(mService.getBaseState(), DEFAULT_DEVICE_STATE); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + } + + @Test + public void requestState_sameAsBaseState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + } + + @Test public void requestState_flagCancelWhenBaseChanges() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); @@ -407,6 +488,14 @@ public final class DeviceStateManagerServiceTest { } } + public void resumeConfigureOnce() { + if (mPendingConfigureCompleteRunnable != null) { + Runnable onComplete = mPendingConfigureCompleteRunnable; + mPendingConfigureCompleteRunnable = null; + onComplete.run(); + } + } + public int getMostRecentRequestedStateToConfigure() { return mLastDeviceStateRequestedToConfigure; } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 81b2381cd629..15ada896512b 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -88,6 +88,7 @@ public class DisplayModeDirectorTest { private static final String TAG = "DisplayModeDirectorTest"; private static final boolean DEBUG = false; private static final float FLOAT_TOLERANCE = 0.01f; + private static final int DISPLAY_ID = 0; private Context mContext; private FakesInjector mInjector; @@ -107,19 +108,29 @@ public class DisplayModeDirectorTest { private DisplayModeDirector createDirectorFromRefreshRateArray( float[] refreshRates, int baseModeId) { + return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]); + } + + private DisplayModeDirector createDirectorFromRefreshRateArray( + float[] refreshRates, int baseModeId, float defaultRefreshRate) { DisplayModeDirector director = new DisplayModeDirector(mContext, mHandler, mInjector); - int displayId = 0; Display.Mode[] modes = new Display.Mode[refreshRates.length]; + Display.Mode defaultMode = null; for (int i = 0; i < refreshRates.length; i++) { modes[i] = new Display.Mode( /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]); + if (refreshRates[i] == defaultRefreshRate) { + defaultMode = modes[i]; + } } + assertThat(defaultMode).isNotNull(); + SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>(); - supportedModesByDisplay.put(displayId, modes); + supportedModesByDisplay.put(DISPLAY_ID, modes); director.injectSupportedModesByDisplay(supportedModesByDisplay); SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>(); - defaultModesByDisplay.put(displayId, modes[0]); + defaultModesByDisplay.put(DISPLAY_ID, defaultMode); director.injectDefaultModeByDisplay(defaultModesByDisplay); return director; } @@ -130,16 +141,15 @@ public class DisplayModeDirectorTest { for (int i = 0; i < numRefreshRates; i++) { refreshRates[i] = minFps + i; } - return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps); + return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps, + /*defaultRefreshRate=*/minFps); } @Test public void testDisplayModeVoting() { - int displayId = 0; - // With no votes present, DisplayModeDirector should allow any refresh rate. DesiredDisplayModeSpecs modeSpecs = - createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId); + createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(modeSpecs.baseModeId).isEqualTo(60); assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f); assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY); @@ -156,12 +166,12 @@ public class DisplayModeDirectorTest { assertTrue(2 * numPriorities < maxFps - minFps + 1); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); for (int i = 0; i < numPriorities; i++) { int priority = Vote.MIN_PRIORITY + i; votes.put(priority, Vote.forRefreshRates(minFps + i, maxFps - i)); director.injectVotesByDisplay(votesByDisplay); - modeSpecs = director.getDesiredDisplayModeSpecs(displayId); + modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i); assertThat(modeSpecs.primaryRefreshRateRange.min) .isEqualTo((float) (minFps + i)); @@ -177,11 +187,11 @@ public class DisplayModeDirectorTest { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.MAX_PRIORITY, Vote.forRefreshRates(65, 85)); votes.put(Vote.MIN_PRIORITY, Vote.forRefreshRates(70, 80)); director.injectVotesByDisplay(votesByDisplay); - modeSpecs = director.getDesiredDisplayModeSpecs(displayId); + modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(modeSpecs.baseModeId).isEqualTo(70); assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(70f); assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(80f); @@ -190,18 +200,17 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithFloatingPointErrors() { - int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(50, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); float error = FLOAT_TOLERANCE / 4; votes.put(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, Vote.forRefreshRates(0, 60)); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forRefreshRates(60 + error, 60 + error)); votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60 - error, 60 - error)); director.injectVotesByDisplay(votesByDisplay); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -213,15 +222,14 @@ public class DisplayModeDirectorTest { assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_SIZE); - int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(60, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -229,7 +237,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); @@ -237,7 +245,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); @@ -245,7 +253,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60)); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); } @@ -261,14 +269,13 @@ public class DisplayModeDirectorTest { assertTrue(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); - int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(60, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); @@ -277,7 +284,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(90, Float.POSITIVE_INFINITY)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); @@ -285,7 +292,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(75, 75)); director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.appRequestRefreshRateRange.min) @@ -355,11 +362,10 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithAlwaysRespectAppRequest() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(50, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(0, 60)); votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(60, 90)); votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90)); @@ -369,7 +375,7 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse(); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -377,7 +383,7 @@ public class DisplayModeDirectorTest { director.setShouldAlwaysRespectAppRequestedMode(true); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue(); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.baseModeId).isEqualTo(90); @@ -385,7 +391,7 @@ public class DisplayModeDirectorTest { director.setShouldAlwaysRespectAppRequestedMode(false); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse(); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.baseModeId).isEqualTo(60); @@ -393,11 +399,10 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithSwitchingTypeNone() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(0, 90); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); - votesByDisplay.put(displayId, votes); + votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(30, 90)); votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); @@ -405,7 +410,7 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) .isNotEqualTo(DisplayManager.SWITCHING_TYPE_NONE); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); @@ -417,7 +422,7 @@ public class DisplayModeDirectorTest { assertThat(director.getModeSwitchingType()) .isEqualTo(DisplayManager.SWITCHING_TYPE_NONE); - desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(30); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(30); @@ -427,29 +432,38 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithSwitchingTypeWithinGroups() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(0, 90); director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS); assertThat(director.getModeSwitchingType()) .isEqualTo(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.allowGroupSwitching).isFalse(); } @Test public void testVotingWithSwitchingTypeWithinAndAcrossGroups() { - final int displayId = 0; DisplayModeDirector director = createDirectorFromFpsRange(0, 90); director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS); assertThat(director.getModeSwitchingType()) .isEqualTo(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS); - DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.allowGroupSwitching).isTrue(); } @Test + public void testDefaultDisplayModeIsSelectedIfAvailable() { + final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f}; + final int defaultModeId = 3; + DisplayModeDirector director = createDirectorFromRefreshRateArray( + refreshRates, /*baseModeId=*/0, refreshRates[defaultModeId]); + + DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(specs.baseModeId).isEqualTo(defaultModeId); + } + + @Test public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java new file mode 100644 index 000000000000..bcd853c76a79 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED; +import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED; +import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED; +import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; +import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; + +import android.app.PropertyInvalidatedCache; +import android.content.Context; +import android.os.Parcel; +import android.os.Process; +import android.platform.test.annotations.Presubmit; +import android.view.Display; +import android.view.DisplayAddress; +import android.view.DisplayInfo; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LogicalDisplayMapperTest { + private static int sUniqueTestDisplayId = 0; + + private DisplayDeviceRepository mDisplayDeviceRepo; + private LogicalDisplayMapper mLogicalDisplayMapper; + private Context mContext; + + @Mock LogicalDisplayMapper.Listener mListenerMock; + + @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; + + @Before + public void setUp() { + // Share classloader to allow package private access. + System.setProperty("dexmaker.share_classloader", "true"); + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getContext(); + mDisplayDeviceRepo = new DisplayDeviceRepository( + new DisplayManagerService.SyncRoot(), + new PersistentDataStore(new PersistentDataStore.Injector() { + @Override + public InputStream openRead() { + return null; + } + + @Override + public OutputStream startWrite() { + return null; + } + + @Override + public void finishWrite(OutputStream os, boolean success) {} + })); + + // Disable binder caches in this process. + PropertyInvalidatedCache.disableForTestMode(); + + mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock); + } + + + ///////////////// + // Test Methods + ///////////////// + + @Test + public void testDisplayDeviceAddAndRemove_Internal() { + DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + + // add + LogicalDisplay displayAdded = add(device); + assertEquals(info(displayAdded).address, info(device).address); + assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + + // remove + mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); + verify(mListenerMock).onLogicalDisplayEventLocked( + mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); + LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); + assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + assertEquals(displayAdded, displayRemoved); + } + + @Test + public void testDisplayDeviceAddAndRemove_NonInternalTypes() { + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL); + testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN); + + // Call the internal test again, just to verify that adding non-internal displays + // doesn't affect the ability for an internal display to become the default display. + testDisplayDeviceAddAndRemove_Internal(); + } + + @Test + public void testDisplayDeviceAdd_TwoInternalOneDefault() { + DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0); + DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + + LogicalDisplay display1 = add(device1); + assertEquals(info(display1).address, info(device1).address); + assertNotEquals(Display.DEFAULT_DISPLAY, id(display1)); + + LogicalDisplay display2 = add(device2); + assertEquals(info(display2).address, info(device2).address); + assertEquals(Display.DEFAULT_DISPLAY, id(display2)); + } + + @Test + public void testDisplayDeviceAdd_TwoInternalBothDefault() { + DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY); + + LogicalDisplay display1 = add(device1); + assertEquals(info(display1).address, info(device1).address); + assertEquals(Display.DEFAULT_DISPLAY, id(display1)); + + LogicalDisplay display2 = add(device2); + assertEquals(info(display2).address, info(device2).address); + // Despite the flags, we can only have one default display + assertNotEquals(Display.DEFAULT_DISPLAY, id(display2)); + } + + @Test + public void testGetDisplayIdsLocked() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0)); + add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); + + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID); + assertEquals(3, ids.length); + Arrays.sort(ids); + assertEquals(Display.DEFAULT_DISPLAY, ids[0]); + } + + @Test + public void testSingleDisplayGroup() { + LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0)); + LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); + + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); + } + + @Test + public void testMultipleDisplayGroups() { + LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0)); + + + TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + LogicalDisplay display3 = add(device3); + + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); + assertNotEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); + + // Now switch it back to the default group by removing the flag and issuing an update + DisplayDeviceInfo info = device3.getSourceInfo(); + info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP; + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + + // Verify the new group is correct. + assertEquals(Display.DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); + } + + + ///////////////// + // Helper Methods + ///////////////// + + private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) { + return createDisplayDevice(new DisplayAddressImpl(), type, width, height, flags); + } + + private TestDisplayDevice createDisplayDevice( + DisplayAddress address, int type, int width, int height, int flags) { + TestDisplayDevice device = new TestDisplayDevice(); + DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo(); + displayDeviceInfo.type = type; + displayDeviceInfo.width = width; + displayDeviceInfo.height = height; + displayDeviceInfo.flags = flags; + displayDeviceInfo.address = new DisplayAddressImpl(); + return device; + } + + private DisplayDeviceInfo info(DisplayDevice device) { + return device.getDisplayDeviceInfoLocked(); + } + + private DisplayInfo info(LogicalDisplay display) { + return display.getDisplayInfoLocked(); + } + + private int id(LogicalDisplay display) { + return display.getDisplayIdLocked(); + } + + private LogicalDisplay add(DisplayDevice device) { + mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED); + ArgumentCaptor<LogicalDisplay> displayCaptor = + ArgumentCaptor.forClass(LogicalDisplay.class); + verify(mListenerMock).onLogicalDisplayEventLocked( + displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED)); + clearInvocations(mListenerMock); + return displayCaptor.getValue(); + } + + private void testDisplayDeviceAddAndRemove_NonInternal(int type) { + DisplayDevice device = createDisplayDevice(type, 600, 800, 0); + + // add + LogicalDisplay displayAdded = add(device); + assertEquals(info(displayAdded).address, info(device).address); + assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + + // remove + mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); + verify(mListenerMock).onLogicalDisplayEventLocked( + mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); + LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); + assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + } + + /** + * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific + * display-address implementation in our code. Intentionally uses default object (reference) + * equality rules. + */ + class DisplayAddressImpl extends DisplayAddress { + @Override + public void writeToParcel(Parcel out, int flags) { } + } + + class TestDisplayDevice extends DisplayDevice { + private DisplayDeviceInfo mInfo = new DisplayDeviceInfo(); + private DisplayDeviceInfo mSentInfo; + + TestDisplayDevice() { + super(null, null, "test_display_" + sUniqueTestDisplayId++, mContext); + mInfo = new DisplayDeviceInfo(); + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + if (mSentInfo == null) { + mSentInfo = new DisplayDeviceInfo(); + mSentInfo.copyFrom(mInfo); + } + return mSentInfo; + } + + @Override + public void applyPendingDisplayDeviceInfoChangesLocked() { + mSentInfo = null; + } + + @Override + public boolean hasStableUniqueId() { + return true; + } + + public DisplayDeviceInfo getSourceInfo() { + return mInfo; + } + } +} + diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java deleted file mode 100644 index 275e7c7fec04..000000000000 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.graphics.fonts; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.FileUtils; -import android.platform.test.annotations.Presubmit; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -@Presubmit -@SmallTest -@RunWith(AndroidJUnit4.class) -public final class FontCrashDetectorTest { - - private File mCacheDir; - - @SuppressWarnings("ResultOfMethodCallIgnored") - @Before - public void setUp() { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest"); - FileUtils.deleteContentsAndDir(mCacheDir); - mCacheDir.mkdirs(); - } - - @Test - public void detectCrash() throws Exception { - // Prepare a marker file. - File file = new File(mCacheDir, "detectCrash"); - assertThat(file.createNewFile()).isTrue(); - - FontCrashDetector detector = new FontCrashDetector(file); - assertThat(detector.hasCrashed()).isTrue(); - - detector.clear(); - assertThat(detector.hasCrashed()).isFalse(); - assertThat(file.exists()).isFalse(); - } - - @Test - public void monitorCrash() { - File file = new File(mCacheDir, "monitorCrash"); - FontCrashDetector detector = new FontCrashDetector(file); - assertThat(detector.hasCrashed()).isFalse(); - - FontCrashDetector.MonitoredBlock block = detector.start(); - assertThat(file.exists()).isTrue(); - - block.close(); - assertThat(file.exists()).isFalse(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index f2254a98a70e..c08857ca9bdc 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -74,8 +74,6 @@ public class ActiveSourceActionTest { when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - mHdmiControlService = new HdmiControlService(mContextSpy) { @Override AudioManager getAudioManager() { @@ -106,15 +104,11 @@ public class ActiveSourceActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; Looper looper = mTestLooper.getLooper(); mHdmiControlService.setIoLooper(looper); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 44418ce1e9c4..50ba761cef10 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -76,8 +76,6 @@ public class ArcInitiationActionFromAvrTest { when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - HdmiControlService hdmiControlService = new HdmiControlService(mContextSpy) { @Override @@ -112,11 +110,6 @@ public class ArcInitiationActionFromAvrTest { Looper getServiceLooper() { return mTestLooper.getLooper(); } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @@ -128,6 +121,7 @@ public class ArcInitiationActionFromAvrTest { mHdmiCecLocalDeviceAudioSystem.init(); Looper looper = mTestLooper.getLooper(); hdmiControlService.setIoLooper(looper); + hdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index d454d8771e15..aa5bc933002d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -77,8 +77,6 @@ public class ArcTerminationActionFromAvrTest { when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - HdmiControlService hdmiControlService = new HdmiControlService(mContextSpy) { @Override @@ -113,15 +111,11 @@ public class ArcTerminationActionFromAvrTest { Looper getServiceLooper() { return mTestLooper.getLooper(); } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; Looper looper = mTestLooper.getLooper(); hdmiControlService.setIoLooper(looper); + hdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java index 7cb72c414e52..ef7b274eeb83 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java @@ -86,8 +86,6 @@ public class DevicePowerStatusActionTest { when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - mHdmiControlService = new HdmiControlService(mContextSpy) { @Override AudioManager getAudioManager() { @@ -118,15 +116,11 @@ public class DevicePowerStatusActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; Looper looper = mTestLooper.getLooper(); mHdmiControlService.setIoLooper(looper); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java index 9bf95c0edcdb..678f8b219e28 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java @@ -106,8 +106,6 @@ public class DeviceSelectActionTest { PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, mIThermalServiceMock, new Handler(mMyLooper)); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); - mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override @@ -133,16 +131,12 @@ public class DeviceSelectActionTest { protected PowerManager getPowerManager() { return powerManager; } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService); mHdmiCecLocalDeviceTv.init(); mHdmiControlService.setIoLooper(mMyLooper); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index eedbc958dcd5..6bb148d43a57 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -98,8 +98,6 @@ public class HdmiCecLocalDeviceAudioSystemTest { PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, mIThermalServiceMock, new Handler(mMyLooper)); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); - mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override @@ -188,17 +186,13 @@ public class HdmiCecLocalDeviceAudioSystemTest { protected PowerManager getPowerManager() { return powerManager; } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mHdmiControlService.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); mMyLooper = mTestLooper.getLooper(); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService); mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) { @Override diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index b11ac24b9a29..915392e6eb80 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -87,7 +87,6 @@ public class HdmiCecLocalDevicePlaybackTest { mMyLooper = mTestLooper.getLooper(); PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, mIThermalServiceMock, new Handler(mMyLooper)); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @@ -136,15 +135,11 @@ public class HdmiCecLocalDevicePlaybackTest { protected PowerManager getPowerManager() { return powerManager; } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService); mHdmiCecLocalDevicePlayback.init(); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index 0717112da12c..b3f008598dc8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -128,8 +128,6 @@ public class HdmiCecLocalDeviceTest { Context context = InstrumentationRegistry.getTargetContext(); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); - mHdmiControlService = new HdmiControlService(context) { @Override @@ -163,13 +161,9 @@ public class HdmiCecLocalDeviceTest { void wakeUp() { mWakeupMessageReceived = true; } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mHdmiControlService.setIoLooper(mTestLooper.getLooper()); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 4623eb5b7d4b..4b3ef2f2cfd1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -81,8 +81,6 @@ public class HdmiCecLocalDeviceTvTest { PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, mIThermalServiceMock, new Handler(mMyLooper)); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); - mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override @@ -119,16 +117,12 @@ public class HdmiCecLocalDeviceTvTest { AudioManager getAudioManager() { return mAudioManager; } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService); mHdmiCecLocalDeviceTv.init(); mHdmiControlService.setIoLooper(mMyLooper); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java index 06373c2284b2..1c7ff421fe92 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java @@ -62,12 +62,12 @@ public class HdmiCecPowerStatusControllerTest { private FakeNativeWrapper mNativeWrapper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); - private int mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; @Mock private IPowerManager mIPowerManagerMock; @Mock private IThermalService mIThermalServiceMock; private HdmiControlService mHdmiControlService; + private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback; @Before public void setUp() throws Exception { @@ -81,8 +81,6 @@ public class HdmiCecPowerStatusControllerTest { when(contextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(contextSpy); - mHdmiControlService = new HdmiControlService(contextSpy) { @Override boolean isControlEnabled() { @@ -100,33 +98,24 @@ public class HdmiCecPowerStatusControllerTest { } @Override - int getCecVersion() { - return mHdmiCecVersion; - } - - @Override boolean isPowerStandby() { return false; } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); - HdmiCecLocalDevicePlayback hdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback( + mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback( mHdmiControlService); - hdmiCecLocalDevicePlayback.init(); + mHdmiCecLocalDevicePlayback.init(); mHdmiControlService.setIoLooper(myLooper); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(contextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - mLocalDevices.add(hdmiCecLocalDevicePlayback); + mLocalDevices.add(mHdmiCecLocalDevicePlayback); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false); @@ -188,77 +177,84 @@ public class HdmiCecPowerStatusControllerTest { @Test public void setPowerStatus_doesntSendBroadcast_1_4() { + setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B); mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( - Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, + mHdmiCecLocalDevicePlayback.mAddress, Constants.ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_ON); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus); } @Test public void setPowerStatus_transient_doesntSendBroadcast_1_4() { + setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B); mHdmiCecPowerStatusController.setPowerStatus( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( - Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, + mHdmiCecLocalDevicePlayback.mAddress, Constants.ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus); } @Test public void setPowerStatus_fast_transient_doesntSendBroadcast_1_4() { + setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B); mHdmiCecPowerStatusController.setPowerStatus( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( - Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, + mHdmiCecLocalDevicePlayback.mAddress, Constants.ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus); } @Test public void setPowerStatus_sendsBroadcast_2_0() { - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; - + setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0); mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( - Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, + mHdmiCecLocalDevicePlayback.mAddress, Constants.ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_ON); assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus); } @Test public void setPowerStatus_transient_sendsBroadcast_2_0() { - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; - + setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0); mHdmiCecPowerStatusController.setPowerStatus( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( - Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, + mHdmiCecLocalDevicePlayback.mAddress, Constants.ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus); } @Test public void setPowerStatus_fast_transient_doesntSendBroadcast_2_0() { - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; - + setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0); mHdmiCecPowerStatusController.setPowerStatus( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( - Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, + mHdmiCecLocalDevicePlayback.mAddress, Constants.ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus); } + + private void setCecVersion(int version) { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, version); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + } } 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 32a70480c39e..47f3bf9daba7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -19,6 +19,7 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -44,12 +45,12 @@ import android.os.RemoteException; 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; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -68,14 +69,64 @@ import java.util.Optional; @RunWith(JUnit4.class) public class HdmiControlServiceTest { - private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDeviceSource { + private class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { private boolean mCanGoToStandby; private boolean mIsStandby; private boolean mIsDisabled; - protected HdmiCecLocalDeviceMyDevice(HdmiControlService service, int deviceType) { - super(service, deviceType); + MockPlaybackDevice(HdmiControlService service) { + super(service); + } + + @Override + protected void onAddressAllocated(int logicalAddress, int reason) {} + + @Override + protected int getPreferredAddress() { + return 0; + } + + @Override + protected void setPreferredAddress(int addr) {} + + @Override + protected boolean canGoToStandby() { + return mCanGoToStandby; + } + + @Override + protected void disableDevice( + boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { + mIsDisabled = true; + originalCallback.onCleared(this); + } + + @Override + protected void onStandby(boolean initiatedByCec, int standbyAction) { + mIsStandby = true; + } + + protected boolean isStandby() { + return mIsStandby; + } + + protected boolean isDisabled() { + return mIsDisabled; + } + + protected void setCanGoToStandby(boolean canGoToStandby) { + mCanGoToStandby = canGoToStandby; + } + } + private class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem { + + private boolean mCanGoToStandby; + private boolean mIsStandby; + private boolean mIsDisabled; + + MockAudioSystemDevice(HdmiControlService service) { + super(service); } @Override @@ -123,8 +174,8 @@ public class HdmiControlServiceTest { private Context mContextSpy; private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; - private HdmiCecLocalDeviceMyDevice mMyAudioSystemDevice; - private HdmiCecLocalDeviceMyDevice mMyPlaybackDevice; + private MockAudioSystemDevice mAudioSystemDevice; + private MockPlaybackDevice mPlaybackDevice; private FakeNativeWrapper mNativeWrapper; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); @@ -144,6 +195,7 @@ public class HdmiControlServiceTest { PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, mIThermalServiceMock, null); when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); + when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); @@ -157,21 +209,17 @@ public class HdmiControlServiceTest { @Override protected void writeStringSystemProperty(String key, String value) { } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; mMyLooper = mTestLooper.getLooper(); - mMyAudioSystemDevice = - new HdmiCecLocalDeviceMyDevice(mHdmiControlService, DEVICE_AUDIO_SYSTEM); - mMyPlaybackDevice = new HdmiCecLocalDeviceMyDevice(mHdmiControlService, DEVICE_PLAYBACK); - mMyAudioSystemDevice.init(); - mMyPlaybackDevice.init(); + mAudioSystemDevice = new MockAudioSystemDevice(mHdmiControlService); + mPlaybackDevice = new MockPlaybackDevice(mHdmiControlService); + mAudioSystemDevice.init(); + mPlaybackDevice.init(); mHdmiControlService.setIoLooper(mMyLooper); + mHdmiControlService.setHdmiCecConfig(hdmiCecConfig); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( @@ -180,8 +228,8 @@ public class HdmiControlServiceTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - mLocalDevices.add(mMyAudioSystemDevice); - mLocalDevices.add(mMyPlaybackDevice); + mLocalDevices.add(mAudioSystemDevice); + mLocalDevices.add(mPlaybackDevice); mHdmiPortInfo = new HdmiPortInfo[4]; mHdmiPortInfo[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); @@ -192,6 +240,9 @@ public class HdmiControlServiceTest { mHdmiPortInfo[3] = new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, + HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); @@ -201,13 +252,13 @@ public class HdmiControlServiceTest { @Test public void onStandby_notByCec_cannotGoToStandby() { mStandbyMessageReceived = false; - mMyPlaybackDevice.setCanGoToStandby(false); + mPlaybackDevice.setCanGoToStandby(false); mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); - assertTrue(mMyPlaybackDevice.isStandby()); - assertTrue(mMyAudioSystemDevice.isStandby()); - assertFalse(mMyPlaybackDevice.isDisabled()); - assertFalse(mMyAudioSystemDevice.isDisabled()); + assertTrue(mPlaybackDevice.isStandby()); + assertTrue(mAudioSystemDevice.isStandby()); + assertFalse(mPlaybackDevice.isDisabled()); + assertFalse(mAudioSystemDevice.isDisabled()); } @Test @@ -215,10 +266,10 @@ public class HdmiControlServiceTest { mStandbyMessageReceived = true; mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); - assertTrue(mMyPlaybackDevice.isStandby()); - assertTrue(mMyAudioSystemDevice.isStandby()); - assertTrue(mMyPlaybackDevice.isDisabled()); - assertTrue(mMyAudioSystemDevice.isDisabled()); + assertTrue(mPlaybackDevice.isStandby()); + assertTrue(mAudioSystemDevice.isStandby()); + assertTrue(mPlaybackDevice.isDisabled()); + assertTrue(mAudioSystemDevice.isDisabled()); } @Test @@ -275,6 +326,7 @@ public class HdmiControlServiceTest { mHdmiControlService.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); + mTestLooper.dispatchAll(); mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); mNativeWrapper.clearResultMessages(); @@ -555,8 +607,8 @@ public class HdmiControlServiceTest { HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mMyPlaybackDevice.getRcProfile(), mMyPlaybackDevice.getRcFeatures(), - mMyPlaybackDevice.getDeviceFeatures()); + mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), + mPlaybackDevice.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures); } @@ -573,8 +625,8 @@ public class HdmiControlServiceTest { HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mMyPlaybackDevice.getRcProfile(), mMyPlaybackDevice.getRcFeatures(), - mMyPlaybackDevice.getDeviceFeatures()); + mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), + mPlaybackDevice.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportFeatures); } @@ -590,8 +642,8 @@ public class HdmiControlServiceTest { HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mMyPlaybackDevice.getRcProfile(), mMyPlaybackDevice.getRcFeatures(), - mMyPlaybackDevice.getDeviceFeatures()); + mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), + mPlaybackDevice.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures); } @@ -612,41 +664,42 @@ public class HdmiControlServiceTest { assertEquals(runnerUid, Binder.getCallingWorkSourceUid()); } - @Ignore("b/180499471") @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); - mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.initService(); + mTestLooper.dispatchAll(); assertThat(mHdmiControlService.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } - @Ignore("b/180499471") @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); - mNativeWrapper.setCecVersion(0x0); - mHdmiControlService.initService(); + mTestLooper.dispatchAll(); assertThat(mHdmiControlService.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } - @Ignore("b/180499471") @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); - mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.initService(); + mTestLooper.dispatchAll(); assertThat(mHdmiControlService.getCecVersion()).isEqualTo( 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 b8dfd5672056..605f781b23df 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -84,8 +84,6 @@ public class PowerStatusMonitorActionTest { when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - mHdmiControlService = new HdmiControlService(mContextSpy) { @Override AudioManager getAudioManager() { @@ -116,15 +114,11 @@ public class PowerStatusMonitorActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; Looper looper = mTestLooper.getLooper(); mHdmiControlService.setIoLooper(looper); + mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); @@ -185,6 +179,7 @@ public class PowerStatusMonitorActionTest { mHdmiControlService.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); + mTestLooper.dispatchAll(); sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000); reportPowerStatus(ADDR_PLAYBACK_1, true, HdmiControlManager.POWER_STATUS_ON); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index f9160abcbfbf..e82c788020ed 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -65,8 +65,6 @@ public class SystemAudioInitiationActionFromAvrTest { Context context = InstrumentationRegistry.getTargetContext(); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); - HdmiControlService hdmiControlService = new HdmiControlService(context) { @Override void sendCecCommand( @@ -164,15 +162,11 @@ public class SystemAudioInitiationActionFromAvrTest { int pathToPortId(int path) { return -1; } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } }; Looper looper = mTestLooper.getLooper(); hdmiControlService.setIoLooper(looper); + hdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); HdmiCecController.NativeWrapper nativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( hdmiControlService, nativeWrapper, hdmiControlService.getAtomWriter()); diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index deaeb46c4074..8b35af80e47f 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -276,7 +276,7 @@ public class JobStoreTest { 0 /* sourceUserId */, 0, "someTag", invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, - persistedExecutionTimesUTC, 0 /* innerFlagg */); + persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */); mTaskStoreUnderTest.add(js); waitForPendingIo(); diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java index f87d5993c1b5..dd9ae6592f5c 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java @@ -19,14 +19,18 @@ package com.android.server.job; import static com.android.server.job.JobConcurrencyManager.NUM_WORK_TYPES; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; +import static com.android.server.job.JobConcurrencyManager.workTypeToString; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.annotation.NonNull; +import android.util.Log; import android.util.Pair; import android.util.SparseIntArray; @@ -50,11 +54,11 @@ import java.util.Random; @RunWith(AndroidJUnit4.class) @MediumTest public class WorkCountTrackerTest { - private static final String TAG = "WorkerCountTrackerTest"; + private static final String TAG = "WorkCountTrackerTest"; private static final double[] EQUAL_PROBABILITY_CDF = buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, - 1.0 / NUM_WORK_TYPES); + 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES); private Random mRandom; private WorkCountTracker mWorkCountTracker; @@ -66,12 +70,16 @@ public class WorkCountTrackerTest { } @NonNull - private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) { - return buildCdf(pTop, pEj, pBg, pBgUser); + private static double[] buildWorkTypeCdf( + double pTop, double pFgs, double pEj, double pBg, double pBgUserImp, double pBgUser) { + return buildCdf(pTop, pFgs, pEj, pBg, pBgUserImp, pBgUser); } @NonNull private static double[] buildCdf(double... probs) { + if (probs.length == 0) { + throw new IllegalArgumentException("Must supply at least one probability"); + } double[] cdf = new double[probs.length]; double sum = 0; @@ -81,7 +89,9 @@ public class WorkCountTrackerTest { } if (Double.compare(1, sum) != 0) { - throw new IllegalArgumentException("probabilities don't sum to one: " + sum); + Log.e(TAG, "probabilities don't sum to one: " + sum); + // 1.0/6 doesn't work well in code :/ + cdf[cdf.length - 1] = 1; } return cdf; } @@ -102,10 +112,14 @@ public class WorkCountTrackerTest { case 0: return WORK_TYPE_TOP; case 1: - return WORK_TYPE_EJ; + return WORK_TYPE_FGS; case 2: - return WORK_TYPE_BG; + return WORK_TYPE_EJ; case 3: + return WORK_TYPE_BG; + case 4: + return WORK_TYPE_BGUSER_IMPORTANT; + case 5: return WORK_TYPE_BGUSER; default: throw new IllegalStateException("Unknown work type"); @@ -224,12 +238,15 @@ public class WorkCountTrackerTest { private void startPendingJobs(Jobs jobs) { while (hasStartablePendingJob(jobs)) { - final int startingWorkType = - getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble()); + final int workType = getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble()); + + if (jobs.pending.get(workType) > 0) { + final int pendingMultiType = getPendingMultiType(jobs, workType); + final int startingWorkType = mWorkCountTracker.canJobStart(pendingMultiType); + if (startingWorkType == WORK_TYPE_NONE) { + continue; + } - if (jobs.pending.get(startingWorkType) > 0 - && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) { - final int pendingMultiType = getPendingMultiType(jobs, startingWorkType); jobs.removePending(pendingMultiType); jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1); mWorkCountTracker.stageJob(startingWorkType, pendingMultiType); @@ -304,7 +321,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0.5, 0, 0); final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; @@ -322,7 +339,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3); final double[] numTypesCdf = buildCdf(.75, .2, .05); final double probStart = 0.5; @@ -340,7 +357,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3); final double[] numTypesCdf = buildCdf(.05, .95); final double probStart = 0.5; @@ -358,7 +375,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.8, 0.02, .08); final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; @@ -376,7 +393,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0); + final double[] cdf = buildWorkTypeCdf(0.85, 0.05, 0, 0.1, 0, 0); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -394,7 +411,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.1, 0.05, .75); final double[] numTypesCdf = buildCdf(0.5, 0.5); final double probStart = 0.5; @@ -413,7 +430,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05); + final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0.05, 0, 0.05); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -432,7 +449,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.5, 0, 0.5); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -451,7 +468,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.1, 0, 0.9); final double[] numTypesCdf = buildCdf(0.9, 0.1); final double probStart = 0.5; @@ -470,7 +487,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.9, 0, 0.1); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -488,7 +505,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0, 0, 0); final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2); final double probStart = 0.5; @@ -511,7 +528,7 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)); final double probStop = 0.13; - final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85); + final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.7, 0.1, 0.05); final double probStart = 0.87; checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, @@ -528,7 +545,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05); + final double[] cdf = buildWorkTypeCdf(.1, 0, 0.5, 0.35, 0, 0.05); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -548,7 +565,28 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4); + final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.4, 0.1, 0, 0.4); + final double[] numTypesCdf = buildCdf(0.7, 0.3); + final double probStart = 0.5; + + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); + } + + @Test + public void testRandom16() { + final Jobs jobs = new Jobs(); + + final int numTests = 5000; + final int totalMax = 7; + final List<Pair<Integer, Integer>> maxLimits = + List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), + Pair.create(WORK_TYPE_BGUSER, 1)); + final List<Pair<Integer, Integer>> minLimits = + List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)); + final double probStop = 0.4; + final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.25, 0.05, 0.3, 0.3); final double[] numTypesCdf = buildCdf(0.7, 0.3); final double probStart = 0.5; @@ -576,11 +614,13 @@ public class WorkCountTrackerTest { startPendingJobs(jobs); for (Pair<Integer, Integer> run : resultRunning) { - assertWithMessage("Incorrect running result for work type " + run.first) + assertWithMessage( + "Incorrect running result for work type " + workTypeToString(run.first)) .that(jobs.running.get(run.first)).isEqualTo(run.second); } for (Pair<Integer, Integer> pend : resultPending) { - assertWithMessage("Incorrect pending result for work type " + pend.first) + assertWithMessage( + "Incorrect pending result for work type " + workTypeToString(pend.first)) .that(jobs.pending.get(pend.first)).isEqualTo(pend.second); } } @@ -738,6 +778,7 @@ public class WorkCountTrackerTest { /* resPen */ List.of( Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2))); + Log.d(TAG, "START***#*#*#*#*#*#**#*"); // Test multi-types checkSimple(6, /* min */ List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)), @@ -754,7 +795,10 @@ public class WorkCountTrackerTest { /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)), /* resPen */ List.of( - Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1))); + // Not checking BG count because the test starts jobs in random order + // and if it tries to start 4 BG jobs (2 will run as EJ from EJ|BG), but + // the resulting pending will be 3 BG instead of 4 BG. + Pair.create(WORK_TYPE_BGUSER, 1))); } /** Tests that the counter updates properly when jobs are stopped. */ @@ -938,10 +982,15 @@ public class WorkCountTrackerTest { assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6); assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1); assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); - assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4); + // If run the TOP jobs as TOP first, and a TOP|EJ job as EJ, then we'll have 4 TOP jobs + // remaining. + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtLeast(4); + // If we end up running the TOP|EJ jobs as TOP first, then we'll have 5 TOP jobs remaining. + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtMost(5); // Can't equate pending EJ since some could be running as TOP and BG assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); - assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(8); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(9); // Stop all jobs jobs.maybeFinishJobs(1); @@ -975,7 +1024,7 @@ public class WorkCountTrackerTest { assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1); assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); - assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(1); assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4); } } diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java index 2288a8925561..a5fedef23e00 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java @@ -17,8 +17,11 @@ package com.android.server.job; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; +import static com.android.server.job.JobConcurrencyManager.workTypeToString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -44,12 +47,16 @@ import java.util.List; public class WorkTypeConfigTest { private static final String KEY_MAX_TOTAL = "concurrency_max_total_test"; private static final String KEY_MAX_TOP = "concurrency_max_top_test"; + private static final String KEY_MAX_FGS = "concurrency_max_fgs_test"; private static final String KEY_MAX_EJ = "concurrency_max_ej_test"; private static final String KEY_MAX_BG = "concurrency_max_bg_test"; + private static final String KEY_MAX_BGUSER_IMPORTANT = "concurrency_max_bguser_important_test"; private static final String KEY_MAX_BGUSER = "concurrency_max_bguser_test"; private static final String KEY_MIN_TOP = "concurrency_min_top_test"; + private static final String KEY_MIN_FGS = "concurrency_min_fgs_test"; private static final String KEY_MIN_EJ = "concurrency_min_ej_test"; private static final String KEY_MIN_BG = "concurrency_min_bg_test"; + private static final String KEY_MIN_BGUSER_IMPORTANT = "concurrency_min_bguser_important_test"; private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test"; @After @@ -59,15 +66,21 @@ public class WorkTypeConfigTest { private void resetConfig() { // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually. - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_FGS, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_MAX_BGUSER_IMPORTANT, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_FGS, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_MIN_BGUSER_IMPORTANT, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, null, false); } private void check(@Nullable DeviceConfig.Properties config, @@ -103,10 +116,12 @@ public class WorkTypeConfigTest { assertEquals(expectedTotal, counts.getMaxTotal()); for (Pair<Integer, Integer> min : expectedMinLimits) { - assertEquals((int) min.second, counts.getMinReserved(min.first)); + assertEquals("Incorrect min value for " + workTypeToString(min.first), + (int) min.second, counts.getMinReserved(min.first)); } for (Pair<Integer, Integer> max : expectedMaxLimits) { - assertEquals((int) max.second, counts.getMax(max.first)); + assertEquals("Incorrect max value for " + workTypeToString(max.first), + (int) max.second, counts.getMax(max.first)); } } @@ -193,6 +208,14 @@ public class WorkTypeConfigTest { /* min */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 1)), /* max */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1))); + check(null, /*default*/ 10, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)), + /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3)), + /*expected*/ true, 10, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)), + /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3))); check(null, /*default*/ 15, /* min */ List.of(Pair.create(WORK_TYPE_BG, 15)), /* max */ List.of(Pair.create(WORK_TYPE_BG, 15)), @@ -289,5 +312,34 @@ public class WorkTypeConfigTest { /*expected*/ true, 16, /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_BG, 8)), /* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16))); + + check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) + .setInt(KEY_MAX_TOTAL, 16) + .setInt(KEY_MAX_TOP, 16) + .setInt(KEY_MIN_TOP, 1) + .setInt(KEY_MAX_FGS, 15) + .setInt(KEY_MIN_FGS, 2) + .setInt(KEY_MAX_EJ, 14) + .setInt(KEY_MIN_EJ, 3) + .setInt(KEY_MAX_BG, 13) + .setInt(KEY_MIN_BG, 4) + .setInt(KEY_MAX_BGUSER_IMPORTANT, 12) + .setInt(KEY_MIN_BGUSER_IMPORTANT, 5) + .setInt(KEY_MAX_BGUSER, 11) + .setInt(KEY_MIN_BGUSER, 6) + .build(), + /*default*/ 9, + /* min */ List.of(Pair.create(WORK_TYPE_BG, 9)), + /* max */ List.of(Pair.create(WORK_TYPE_BG, 9)), + /*expected*/ true, 16, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 4), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 5), + Pair.create(WORK_TYPE_BGUSER, 6)), + /* max */ + List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15), + Pair.create(WORK_TYPE_EJ, 14), Pair.create(WORK_TYPE_BG, 13), + Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 12), + Pair.create(WORK_TYPE_BGUSER, 11))); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index a896f1b0d60f..b51f4df43259 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -44,6 +44,7 @@ import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; import android.os.Handler; +import android.os.HandlerThread; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserManager; @@ -62,6 +63,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import javax.crypto.SecretKey; @@ -181,6 +183,18 @@ public class RebootEscrowManagerTests { } @Override + public int getLoadEscrowDataRetryLimit() { + // Try two times + return 2; + } + + @Override + public int getLoadEscrowDataRetryIntervalSeconds() { + // Retry in 1 seconds + return 1; + } + + @Override public void reportMetric(boolean success) { mInjected.reportMetric(success); } @@ -448,6 +462,46 @@ public class RebootEscrowManagerTests { } @Test + public void loadRebootEscrowDataIfAvailable_ServerBased_RetrySuccess() throws Exception { + setServerBasedRebootEscrowProvider(); + + when(mInjected.getBootCount()).thenReturn(0); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + // Use x -> x for both wrap & unwrap functions. + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); + + when(mServiceConnection.unwrap(any(), anyLong())) + .thenThrow(new IOException()) + .thenAnswer(invocation -> invocation.getArgument(0)); + + HandlerThread thread = new HandlerThread("RebootEscrowManagerTest"); + thread.start(); + mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper())); + // Sleep 5s for the retry to complete + Thread.sleep(5 * 1000); + verify(mServiceConnection, times(2)).unwrap(any(), anyLong()); + assertTrue(metricsSuccessCaptor.getValue()); + verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); + } + + @Test public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception { when(mInjected.getBootCount()).thenReturn(0); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 2d6605ae222f..c0a38b874914 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -239,8 +239,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertTrue(mService.hasPendingEscrowToken(PRIMARY_USER_ID)); - mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) - .getResponseCode(); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID)); @@ -268,8 +268,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null); assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); - mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) - .getResponseCode(); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mLocalService.setLockCredentialWithToken(nonePassword(), handle, token, PRIMARY_USER_ID); @@ -294,8 +294,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null); assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); - mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) - .getResponseCode(); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredential(pattern, password, PRIMARY_USER_ID); @@ -369,6 +369,36 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { } @Test + public void testActivateMultipleEscrowTokens() throws Exception { + byte[] token0 = "some-high-entropy-secure-token-0".getBytes(); + byte[] token1 = "some-high-entropy-secure-token-1".getBytes(); + byte[] token2 = "some-high-entropy-secure-token-2".getBytes(); + + LockscreenCredential password = newPassword("password"); + LockscreenCredential pattern = newPattern("123654"); + initializeCredentialUnderSP(password, PRIMARY_USER_ID); + + long handle0 = mLocalService.addEscrowToken(token0, PRIMARY_USER_ID, null); + long handle1 = mLocalService.addEscrowToken(token1, PRIMARY_USER_ID, null); + long handle2 = mLocalService.addEscrowToken(token2, PRIMARY_USER_ID, null); + + // Activate token + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); + + // Verify tokens work + assertTrue(mLocalService.isEscrowTokenActive(handle0, PRIMARY_USER_ID)); + assertTrue(mLocalService.setLockCredentialWithToken( + pattern, handle0, token0, PRIMARY_USER_ID)); + assertTrue(mLocalService.isEscrowTokenActive(handle1, PRIMARY_USER_ID)); + assertTrue(mLocalService.setLockCredentialWithToken( + pattern, handle1, token1, PRIMARY_USER_ID)); + assertTrue(mLocalService.isEscrowTokenActive(handle2, PRIMARY_USER_ID)); + assertTrue(mLocalService.setLockCredentialWithToken( + pattern, handle2, token2, PRIMARY_USER_ID)); + } + + @Test public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception { LockscreenCredential password = newPassword("password"); LockscreenCredential pattern = newPattern("123654"); @@ -494,8 +524,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { reset(mDevicePolicyManager); long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null); - mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) - .getResponseCode(); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.onCleanupUser(PRIMARY_USER_ID); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 74bf4f5da70d..13c3919cefc5 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2041,7 +2041,8 @@ public class NetworkPolicyManagerServiceTest { final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); networkCapabilities.addTransportType(TRANSPORT_WIFI); networkCapabilities.setSSID(TEST_SSID); - return new NetworkState(TYPE_WIFI, prop, networkCapabilities, null, null); + return new NetworkState(TYPE_WIFI, prop, networkCapabilities, new Network(TEST_NET_ID), + null); } private void expectHasInternetPermission(int uid, boolean hasIt) throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index ea27331ac4ca..6e5fbd0b6ed0 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -764,7 +764,7 @@ public class PowerManagerServiceTest { createService(); startSystem(); - mService.getBinderServiceInstance().userActivity(mClock.now(), + mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); verify(mInattentiveSleepWarningControllerMock, never()).show(); @@ -773,7 +773,7 @@ public class PowerManagerServiceTest { verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean()); when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true); - mService.getBinderServiceInstance().userActivity(mClock.now(), + mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); verify(mInattentiveSleepWarningControllerMock, times(1)).dismiss(true); } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 11fb0021be62..624c3de650aa 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -144,6 +144,9 @@ public class AppStandbyControllerTests { private static final long RARE_THRESHOLD = 48 * HOUR_MS; private static final long RESTRICTED_THRESHOLD = 96 * HOUR_MS; + private static final int ASSERT_RETRY_ATTEMPTS = 20; + private static final int ASSERT_RETRY_DELAY_MILLISECONDS = 500; + /** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */ private static boolean isPackageInstalled = true; @@ -589,16 +592,37 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime); } - private void assertBucket(int bucket) { + private void assertBucket(int bucket) throws InterruptedException { assertBucket(bucket, PACKAGE_1); } - private void assertBucket(int bucket, String pkg) { + private void assertBucket(int bucket, String pkg) throws InterruptedException { + int retries = 0; + do { + if (bucket == getStandbyBucket(mController, pkg)) { + // Success + return; + } + Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS); + retries++; + } while(retries < ASSERT_RETRY_ATTEMPTS); + // try one last time assertEquals(bucket, getStandbyBucket(mController, pkg)); } - private void assertNotBucket(int bucket) { - assertNotEquals(bucket, getStandbyBucket(mController, PACKAGE_1)); + private void assertNotBucket(int bucket) throws InterruptedException { + final String pkg = PACKAGE_1; + int retries = 0; + do { + if (bucket != getStandbyBucket(mController, pkg)) { + // Success + return; + } + Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS); + retries++; + } while(retries < ASSERT_RETRY_ATTEMPTS); + // try one last time + assertNotEquals(bucket, getStandbyBucket(mController, pkg)); } @Test @@ -996,7 +1020,7 @@ public class AppStandbyControllerTests { * a low bucket after the RESTRICTED timeout. */ @Test - public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() { + public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); assertBucket(STANDBY_BUCKET_ACTIVE); @@ -1032,7 +1056,7 @@ public class AppStandbyControllerTests { * a low bucket after the RESTRICTED timeout. */ @Test - public void testRestrictedTimeoutOverridesPredictionLowBucket() { + public void testRestrictedTimeoutOverridesPredictionLowBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); // Not long enough to time out into RESTRICTED. @@ -1055,7 +1079,7 @@ public class AppStandbyControllerTests { } @Test - public void testRestrictedBucketDisabled() { + public void testRestrictedBucketDisabled() throws Exception { mInjector.mIsRestrictedBucketEnabled = false; // Get the controller to read the new value. Capturing the ContentObserver isn't possible // at the moment. @@ -1080,7 +1104,7 @@ public class AppStandbyControllerTests { } @Test - public void testRestrictedBucket_EnabledToDisabled() { + public void testRestrictedBucket_EnabledToDisabled() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, @@ -1097,7 +1121,7 @@ public class AppStandbyControllerTests { } @Test - public void testPredictionRaiseFromRestrictedTimeout_highBucket() { + public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); // Way past all timeouts. App times out into RESTRICTED bucket. @@ -1114,7 +1138,7 @@ public class AppStandbyControllerTests { } @Test - public void testPredictionRaiseFromRestrictedTimeout_lowBucket() { + public void testPredictionRaiseFromRestrictedTimeout_lowBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); // Way past all timeouts. App times out into RESTRICTED bucket. @@ -1366,7 +1390,7 @@ public class AppStandbyControllerTests { } @Test - public void testAddActiveDeviceAdmin() { + public void testAddActiveDeviceAdmin() throws Exception { assertActiveAdmins(USER_ID, (String[]) null); assertActiveAdmins(USER_ID2, (String[]) null); @@ -1402,7 +1426,7 @@ public class AppStandbyControllerTests { } @Test - public void isActiveDeviceAdmin() { + public void isActiveDeviceAdmin() throws Exception { assertActiveAdmins(USER_ID, (String[]) null); assertActiveAdmins(USER_ID2, (String[]) null); @@ -1488,7 +1512,7 @@ public class AppStandbyControllerTests { } @Test - public void testAppUpdateOnRestrictedBucketStatus() { + public void testAppUpdateOnRestrictedBucketStatus() throws Exception { // Updates shouldn't change bucket if the app timed out. // Way past all timeouts. App times out into RESTRICTED bucket. reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); @@ -1563,7 +1587,7 @@ public class AppStandbyControllerTests { } @Test - public void testSystemHeadlessAppElevated() { + public void testSystemHeadlessAppElevated() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_SYSTEM_HEADFULL); @@ -1589,7 +1613,7 @@ public class AppStandbyControllerTests { } @Test - public void testWellbeingAppElevated() { + public void testWellbeingAppElevated() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING); assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING); reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); 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 4634e12f1530..2a3c2c46ce4e 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -100,16 +100,17 @@ final class FakeVibratorControllerProvider { return EFFECT_DURATION; } - public void compose(VibrationEffect.Composition.PrimitiveEffect[] effect, + public long compose(VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) { VibrationEffect.Composed composed = new VibrationEffect.Composed(Arrays.asList(effect)); mEffects.add(composed); applyLatency(); - long duration = EFFECT_DURATION * effect.length; + long duration = 0; for (VibrationEffect.Composition.PrimitiveEffect e : effect) { - duration += e.delay; + duration += EFFECT_DURATION + e.delay; } scheduleListener(duration, vibrationId); + return duration; } public void setExternalControl(boolean enabled) { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java index 8c62b7fe235e..3ca90603e9d2 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java @@ -91,6 +91,7 @@ public class InputDeviceDelegateTest { mInputDeviceDelegate = new InputDeviceDelegate( mContextSpy, new Handler(mTestLooper.getLooper())); + mInputDeviceDelegate.onSystemReady(); } @After @@ -99,6 +100,24 @@ public class InputDeviceDelegateTest { } @Test + public void beforeSystemReady_ignoresAnyUpdate() throws Exception { + when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); + InputDeviceDelegate inputDeviceDelegate = new InputDeviceDelegate( + mContextSpy, new Handler(mTestLooper.getLooper())); + + inputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); + assertFalse(inputDeviceDelegate.isAvailable()); + + inputDeviceDelegate.onInputDeviceAdded(1); + assertFalse(inputDeviceDelegate.isAvailable()); + + updateInputDevices(new int[]{1}); + assertFalse(inputDeviceDelegate.isAvailable()); + + verify(mIInputManagerMock, never()).getInputDevice(anyInt()); + } + + @Test public void onInputDeviceAdded_withSettingsDisabled_ignoresNewDevice() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index 1e6ef9137686..b6c11fe62ff6 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -88,6 +88,7 @@ public class VibrationScalerTest { mVibrationSettings = new VibrationSettings( mContextSpy, new Handler(mTestLooper.getLooper())); mVibrationScaler = new VibrationScaler(mContextSpy, mVibrationSettings); + mVibrationSettings.onSystemReady(); } @After diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index d8679876965c..855012459bd6 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -106,6 +106,7 @@ public class VibrationSettingsTest { mAudioManager = mContextSpy.getSystemService(AudioManager.class); mVibrationSettings = new VibrationSettings(mContextSpy, new Handler(mTestLooper.getLooper())); + mVibrationSettings.onSystemReady(); setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); @@ -162,6 +163,23 @@ public class VibrationSettingsTest { } @Test + public void shouldVibrateForRingerMode_beforeSystemReady_returnsFalseOnlyForRingtone() { + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + setRingerMode(AudioManager.RINGER_MODE_MAX); + VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy, + new Handler(mTestLooper.getLooper())); + + assertFalse(vibrationSettings.shouldVibrateForRingerMode( + VibrationAttributes.USAGE_RINGTONE)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode( + VibrationAttributes.USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode( + VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); + } + + @Test public void shouldVibrateForRingerMode_withoutRingtoneUsage_returnsTrue() { assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM)); assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH)); @@ -303,6 +321,37 @@ public class VibrationSettingsTest { } @Test + public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() { + mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + + VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy, + new Handler(mTestLooper.getLooper())); + + assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, + vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity( + VibrationAttributes.USAGE_PHYSICAL_EMULATION)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + } + + @Test public void getDefaultIntensity_returnsIntensityFromVibratorService() { mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index 815aa8ee66ae..bad3e4c2ed92 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.vibrator; 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.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -202,7 +203,7 @@ public class VibratorControllerTest { VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked) VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - controller.on(effect, 11); + assertEquals(10L, controller.on(effect, 11)); assertTrue(controller.isVibrating()); verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK), @@ -212,13 +213,14 @@ public class VibratorControllerTest { @Test public void on_withComposed_performsEffect() { mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L); VibratorController controller = createController(); VibrationEffect.Composed effect = (VibrationEffect.Composed) VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) .compose(); - controller.on(effect, 12); + assertEquals(15L, controller.on(effect, 12)); ArgumentCaptor<VibrationEffect.Composition.PrimitiveEffect[]> primitivesCaptor = ArgumentCaptor.forClass(VibrationEffect.Composition.PrimitiveEffect[].class); 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 ba0a472c80dd..a28d18fb74d3 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -176,8 +176,14 @@ public class VibratorManagerServiceTest { LocalServices.removeServiceForTest(PowerManagerInternal.class); } + private VibratorManagerService createSystemReadyService() { + VibratorManagerService service = createService(); + service.systemReady(); + return service; + } + private VibratorManagerService createService() { - VibratorManagerService service = new VibratorManagerService( + return new VibratorManagerService( mContextSpy, new VibratorManagerService.Injector() { @Override @@ -201,8 +207,6 @@ public class VibratorManagerServiceTest { void addService(String name, IBinder service) { } }); - service.systemReady(); - return service; } @Test @@ -215,21 +219,44 @@ public class VibratorManagerServiceTest { } @Test + public void createService_doNotCrashIfUsedBeforeSystemReady() { + mockVibrators(1, 2); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL); + mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL); + VibratorManagerService service = createService(); + + assertNotNull(service.getVibratorIds()); + assertNotNull(service.getVibratorInfo(1)); + assertFalse(service.isVibrating(1)); + + CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); + service.cancelVibrate(service); + + assertTrue(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + + IVibratorStateListener listener = mockVibratorStateListener(); + assertTrue(service.registerVibratorStateListener(1, listener)); + assertTrue(service.unregisterVibratorStateListener(1, listener)); + } + + @Test public void getVibratorIds_withNullResultFromNative_returnsEmptyArray() { when(mNativeWrapperMock.getVibratorIds()).thenReturn(null); - assertArrayEquals(new int[0], createService().getVibratorIds()); + assertArrayEquals(new int[0], createSystemReadyService().getVibratorIds()); } @Test public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() { mockVibrators(2, 1); - assertArrayEquals(new int[]{2, 1}, createService().getVibratorIds()); + assertArrayEquals(new int[]{2, 1}, createSystemReadyService().getVibratorIds()); } @Test public void getVibratorInfo_withMissingVibratorId_returnsNull() { mockVibrators(1); - assertNull(createService().getVibratorInfo(2)); + assertNull(createSystemReadyService().getVibratorInfo(2)); } @Test @@ -239,7 +266,7 @@ public class VibratorManagerServiceTest { vibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL); vibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); vibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK); - VibratorInfo info = createService().getVibratorInfo(1); + VibratorInfo info = createSystemReadyService().getVibratorInfo(1); assertNotNull(info); assertEquals(1, info.getId()); @@ -257,7 +284,7 @@ public class VibratorManagerServiceTest { @Test public void registerVibratorStateListener_callbacksAreTriggered() throws Exception { mockVibrators(1); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); IVibratorStateListener listenerMock = mockVibratorStateListener(); service.registerVibratorStateListener(1, listenerMock); @@ -278,7 +305,7 @@ public class VibratorManagerServiceTest { @Test public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception { mockVibrators(1); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); IVibratorStateListener listenerMock = mockVibratorStateListener(); service.registerVibratorStateListener(1, listenerMock); @@ -303,7 +330,7 @@ public class VibratorManagerServiceTest { @Test public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception { mockVibrators(0, 1, 2); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); IVibratorStateListener[] listeners = new IVibratorStateListener[3]; for (int i = 0; i < 3; i++) { listeners[i] = mockVibratorStateListener(); @@ -330,7 +357,8 @@ public class VibratorManagerServiceTest { CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); - assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + assertTrue(createSystemReadyService().setAlwaysOnEffect( + UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked( VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); @@ -353,7 +381,8 @@ public class VibratorManagerServiceTest { .addVibrator(2, VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)) .addVibrator(3, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) .combine(); - assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + assertTrue(createSystemReadyService().setAlwaysOnEffect( + UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); VibrationEffect.Prebaked expectedClick = new VibrationEffect.Prebaked( VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); @@ -376,9 +405,11 @@ public class VibratorManagerServiceTest { CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); - assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + assertTrue(createSystemReadyService().setAlwaysOnEffect( + UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); - assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, null, ALARM_ATTRS)); + assertTrue(createSystemReadyService().setAlwaysOnEffect( + UID, PACKAGE_NAME, 1, null, ALARM_ATTRS)); assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1)); assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1)); @@ -392,7 +423,8 @@ public class VibratorManagerServiceTest { CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); - assertFalse(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + assertFalse(createSystemReadyService().setAlwaysOnEffect( + UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1)); } @@ -405,7 +437,8 @@ public class VibratorManagerServiceTest { CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential() .addNext(0, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .combine(); - assertFalse(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + assertFalse(createSystemReadyService().setAlwaysOnEffect( + UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1)); } @@ -413,7 +446,7 @@ public class VibratorManagerServiceTest { @Test public void setAlwaysOnEffect_withNoVibratorWithCapability_ignoresEffect() { mockVibrators(1); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); CombinedVibrationEffect mono = CombinedVibrationEffect.createSynced( VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); @@ -435,18 +468,18 @@ public class VibratorManagerServiceTest { setRingerMode(AudioManager.RINGER_MODE_NORMAL); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); vibrate(service, VibrationEffect.createOneShot(40, 1), RINGTONE_ATTRS); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); - service = createService(); + service = createSystemReadyService(); vibrate(service, VibrationEffect.createOneShot(40, 10), RINGTONE_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); - service = createService(); + service = createSystemReadyService(); vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); @@ -459,7 +492,7 @@ public class VibratorManagerServiceTest { mockVibrators(1); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS); vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS); @@ -480,7 +513,7 @@ public class VibratorManagerServiceTest { @Test public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() { - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); AudioAttributes audioAttributes = new AudioAttributes.Builder() @@ -496,7 +529,7 @@ public class VibratorManagerServiceTest { @Test public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS); @@ -534,7 +567,7 @@ public class VibratorManagerServiceTest { when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( VibrationEffect.createOneShot(10, 10)); @@ -550,7 +583,7 @@ public class VibratorManagerServiceTest { public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); // The native callback will be dispatched manually in this test. mTestLooper.stopAutoDispatchAndIgnoreExceptions(); @@ -573,7 +606,7 @@ public class VibratorManagerServiceTest { mockVibrators(1, 2); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); // The native callback will be dispatched manually in this test. mTestLooper.stopAutoDispatchAndIgnoreExceptions(); @@ -619,7 +652,7 @@ public class VibratorManagerServiceTest { FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) @@ -645,7 +678,7 @@ public class VibratorManagerServiceTest { mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) @@ -665,7 +698,7 @@ public class VibratorManagerServiceTest { mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON); mockVibrators(1, 2); when(mNativeWrapperMock.prepareSynced(any())).thenReturn(false); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.createOneShot(10, 50)) @@ -686,7 +719,7 @@ public class VibratorManagerServiceTest { mockVibrators(1, 2); when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true); when(mNativeWrapperMock.triggerSynced(anyLong())).thenReturn(false); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.createOneShot(10, 50)) @@ -716,7 +749,7 @@ public class VibratorManagerServiceTest { fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL, IVibrator.CAP_COMPOSE_EFFECTS); fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); vibrate(service, CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) @@ -762,7 +795,7 @@ public class VibratorManagerServiceTest { @Test public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception { mockVibrators(1, 2); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); vibrate(service, CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) @@ -780,7 +813,7 @@ public class VibratorManagerServiceTest { @Test public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception { mockVibrators(1); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); @@ -793,7 +826,7 @@ public class VibratorManagerServiceTest { @Test public void cancelVibrate_stopsVibrating() throws Exception { mockVibrators(1); - VibratorManagerService service = createService(); + VibratorManagerService service = createSystemReadyService(); service.cancelVibrate(service); assertFalse(service.isVibrating(1)); 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 137cf6523caf..09a436c59e7b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -518,6 +518,7 @@ public class DisplayContentTests extends WindowTestsBase { TYPE_WALLPAPER, TYPE_APPLICATION); final WindowState wallpaper = windows[0]; assertTrue(wallpaper.mIsWallpaper); + wallpaper.mToken.asWallpaperToken().setVisibility(false); // By default WindowState#mWallpaperVisible is false. assertFalse(wallpaper.isVisible()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 47cf53b621d3..074ef3667857 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -323,4 +323,16 @@ public class DisplayPolicyTests extends WindowTestsBase { assertFalse(navBarSource.getFrame().isEmpty()); assertTrue(imeSource.getFrame().contains(navBarSource.getFrame())); } + + @UseTestDisplay + @Test + public void testDisplayPolicyNotCrash() { + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + + // Verify if modules initialized after DisplayContent ctr throws NPE. + displayPolicy.onDisplayInfoChanged(mDisplayInfo); + displayPolicy.onConfigurationChanged(); + displayPolicy.onOverlayChangedLw(); + displayPolicy.release(); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index be036034542e..80961d7afb70 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -416,6 +416,16 @@ public class InsetsStateControllerTest extends WindowTestsBase { verify(navBar, atLeastOnce()).notifyInsetsChanged(); } + @Test + public void testDispatchGlobalInsets() { + final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR)); + app.mAttrs.receiveInsetsIgnoringZOrder = true; + assertNotNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR)); + } + private WindowState createTestWindow(String name) { final WindowState win = createWindow(null, TYPE_APPLICATION, name); win.setHasSurface(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java index d663b649fbba..cc1869e72b34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java @@ -454,7 +454,7 @@ public class LockTaskControllerTest { Settings.Secure.clearProviderForTest(); // AND a password is set - when(mLockPatternUtils.isSecure(anyInt())) + when(mLockPatternUtils.isSecure(TEST_USER_ID)) .thenReturn(true); // AND there is a task record 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 b73c66407874..2f1d7eb404ad 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -655,7 +655,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Portrait fixed app without max aspect. - prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); + prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT); final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); final Rect activityBounds = new Rect(mActivity.getBounds()); @@ -676,6 +676,96 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMinAspectRatio() { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // 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; + prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT); + + final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); + final Rect activityBounds = new Rect(mActivity.getBounds()); + + // Display shouldn't be rotated. + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, + mActivity.mDisplayContent.getLastOrientation()); + assertTrue(displayBounds.width() > displayBounds.height()); + + // App should launch in fixed orientation letterbox. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertFalse(mActivity.inSizeCompatMode()); + + // Activity bounds should respect minimum aspect ratio for activity. + assertEquals(displayBounds.height(), activityBounds.height()); + assertEquals((int) Math.rint(displayBounds.height() / mActivity.info.minAspectRatio), + activityBounds.width()); + } + + @Test + public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMaxAspectRatio() { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // Portrait fixed app with max aspect ratio lower that aspect ratio override for fixed + // orientation letterbox. + mActivity.mWmService.setFixedOrientationLetterboxAspectRatio(3); + prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT); + + final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); + final Rect activityBounds = new Rect(mActivity.getBounds()); + + // Display shouldn't be rotated. + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, + mActivity.mDisplayContent.getLastOrientation()); + assertTrue(displayBounds.width() > displayBounds.height()); + + // App should launch in fixed orientation letterbox. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertFalse(mActivity.inSizeCompatMode()); + + // Activity bounds should respect maximum aspect ratio for activity. + assertEquals(displayBounds.height(), activityBounds.height()); + assertEquals((int) Math.rint(displayBounds.height() / mActivity.info.maxAspectRatio), + activityBounds.width()); + } + + @Test + public void testDisplayIgnoreOrientationRequest_fixedOrientationAppWithAspectRatioOverride() { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed + // orientation letterbox. + final float fixedOrientationLetterboxAspectRatio = 1.1f; + mActivity.mWmService.setFixedOrientationLetterboxAspectRatio( + fixedOrientationLetterboxAspectRatio); + prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); + + final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); + final Rect activityBounds = new Rect(mActivity.getBounds()); + + // Display shouldn't be rotated. + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, + mActivity.mDisplayContent.getLastOrientation()); + assertTrue(displayBounds.width() > displayBounds.height()); + + // App should launch in fixed orientation letterbox. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertFalse(mActivity.inSizeCompatMode()); + + // Activity bounds should respect aspect ratio override for fixed orientation letterbox. + assertEquals(displayBounds.height(), activityBounds.height()); + assertEquals((int) Math.rint(displayBounds.height() / fixedOrientationLetterboxAspectRatio), + activityBounds.width()); + } + + @Test public void testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() { // Set up a display in landscape and ignoring orientation request. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index a1e5afb8b758..5239462a1ec0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -473,6 +473,68 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { mDefaultDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea); } + @Test + public void testRecalculateFreeformInitialBoundsWithOverrideDisplayArea() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(freeformDisplay, + mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); + secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0, + DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height()); + final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + launchRoot.mCreatedByOrganizer = true; + secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM }, + new int[] { ACTIVITY_TYPE_STANDARD }); + final Rect secondaryDAStableBounds = new Rect(); + secondaryDisplayArea.getStableRect(secondaryDAStableBounds); + + // Specify the display and provide a layout so that it will be set to freeform bounds. + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchDisplayId(freeformDisplay.getDisplayId()); + final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder() + .setGravity(Gravity.LEFT).build(); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setOptions(options).setLayout(layout).calculate()); + + assertEquals(secondaryDisplayArea, mResult.mPreferredTaskDisplayArea); + assertTrue(secondaryDAStableBounds.contains(mResult.mBounds)); + } + + @Test + public void testRecalculateFreeformInitialBoundsWithOverrideDisplayArea_unresizableApp() { + mAtm.mSupportsNonResizableMultiWindow = true; + + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(freeformDisplay, + mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); + secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0, + DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height()); + final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + launchRoot.mCreatedByOrganizer = true; + secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM }, + new int[] { ACTIVITY_TYPE_STANDARD }); + final Rect secondaryDAStableBounds = new Rect(); + secondaryDisplayArea.getStableRect(secondaryDAStableBounds); + + // The bounds will get updated for unresizable with opposite orientation on freeform display + final Rect displayBounds = new Rect(freeformDisplay.getBounds()); + mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; + mActivity.info.screenOrientation = displayBounds.width() > displayBounds.height() + ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchDisplayId(freeformDisplay.getDisplayId()); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setOptions(options).calculate()); + + assertEquals(secondaryDisplayArea, mResult.mPreferredTaskDisplayArea); + assertTrue(secondaryDAStableBounds.contains(mResult.mBounds)); + } + // ===================================== // Launch Windowing Mode Related Tests // ===================================== @@ -521,6 +583,7 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN); } + @Test public void testKeepsPictureInPictureLaunchModeInOptions() { final TestDisplayContent freeformDisplay = createNewDisplayContent( @@ -588,11 +651,14 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test - public void testNonEmptyLayoutInfersFreeformWithEmptySize() { + public void testLayoutWithGravityAndEmptySizeInfersFreeformAndRespectsCurrentSize() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); + final Rect expectedLaunchBounds = new Rect(0, 0, 200, 100); + mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); + mCurrent.mBounds.set(expectedLaunchBounds); final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder() .setGravity(Gravity.LEFT).build(); @@ -600,6 +666,9 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setLayout(layout).calculate()); + assertEquals(expectedLaunchBounds.width(), mResult.mBounds.width()); + assertEquals(expectedLaunchBounds.height(), mResult.mBounds.height()); + assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode, WINDOWING_MODE_FREEFORM); } @@ -1358,8 +1427,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { // This test case requires a relatively big app bounds to ensure the default size calculated // by letterbox won't be too small to hold the minimum width/height. configInsetsState( - freeformDisplay.getInsetsStateController().getRawInsetsState(), - DISPLAY_BOUNDS, new Rect(10, 10, 1910, 1070)); + freeformDisplay.getInsetsStateController().getRawInsetsState(), freeformDisplay, + new Rect(10, 10, 1910, 1070)); final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchDisplayId(freeformDisplay.mDisplayId); @@ -1580,15 +1649,17 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { display.setBounds(DISPLAY_BOUNDS); display.getConfiguration().densityDpi = DENSITY_DEFAULT; display.getConfiguration().orientation = ORIENTATION_LANDSCAPE; - configInsetsState(display.getInsetsStateController().getRawInsetsState(), - DISPLAY_BOUNDS, DISPLAY_STABLE_BOUNDS); + configInsetsState(display.getInsetsStateController().getRawInsetsState(), display, + DISPLAY_STABLE_BOUNDS); return display; } /** * Creates insets sources so that we can get the expected stable frame. */ - private static void configInsetsState(InsetsState state, Rect displayFrame, Rect stableFrame) { + private static void configInsetsState(InsetsState state, DisplayContent display, + Rect stableFrame) { + final Rect displayFrame = display.getBounds(); final int dl = displayFrame.left; final int dt = displayFrame.top; final int dr = displayFrame.right; @@ -1611,6 +1682,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { if (sb < db) { state.getSource(ITYPE_NAVIGATION_BAR).setFrame(dl, sb, dr, db); } + // Recompute config and push to children. + display.onRequestedOverrideConfigurationChanged(display.getConfiguration()); } private ActivityRecord createSourceActivity(TestDisplayContent display) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 401ace03c554..79ef8680dfec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -25,11 +25,14 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; +import static android.window.TransitionInfo.isIndependent; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -316,9 +319,9 @@ public class TransitionTests extends WindowTestsBase { mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */)); final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken, "wallpaperWindow"); - wallpaperWindow.mWallpaperVisible = false; + wallpaperWindowToken.setVisibleRequested(false); transition.collect(wallpaperWindowToken); - wallpaperWindow.mWallpaperVisible = true; + wallpaperWindowToken.setVisibleRequested(true); wallpaperWindow.mHasSurface = true; // doesn't matter which order collected since participants is a set @@ -343,6 +346,76 @@ public class TransitionTests extends WindowTestsBase { tasks[showWallpaperTask].mRemoteToken.toWindowContainerToken()).getFlags()); } + @Test + public void testIndependent() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + ArraySet<WindowContainer> participants = transition.mParticipants; + ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); + + final Task openTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task openInOpenTask = createTaskInStack(openTask, 0); + final ActivityRecord openInOpen = createActivityRecord(openInOpenTask); + + final Task changeTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task changeInChangeTask = createTaskInStack(changeTask, 0); + final Task openInChangeTask = createTaskInStack(changeTask, 0); + final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask); + final ActivityRecord openInChange = createActivityRecord(openInChangeTask); + // set organizer for everything so that they all get added to transition info + for (Task t : new Task[]{ + openTask, openInOpenTask, changeTask, changeInChangeTask, openInChangeTask}) { + t.mTaskOrganizer = mockOrg; + } + + // Start states. + changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(changeTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(openInOpenTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(openInChangeTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(changeInChangeTask, + new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(openInOpen, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(openInChange, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + fillChangeMap(changes, openTask); + // End states. + changeInChange.mVisibleRequested = true; + openInOpen.mVisibleRequested = true; + openInChange.mVisibleRequested = true; + + int transit = TRANSIT_OLD_TASK_OPEN; + int flags = 0; + + // Check full promotion from leaf + participants.add(changeInChange); + participants.add(openInOpen); + participants.add(openInChange); + // Explicitly add changeTask (to test independence with parents) + participants.add(changeTask); + ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); + // Root changes should always be considered independent + assertTrue(isIndependent( + info.getChange(openTask.mRemoteToken.toWindowContainerToken()), info)); + assertTrue(isIndependent( + info.getChange(changeTask.mRemoteToken.toWindowContainerToken()), info)); + + // Children of a open/close change are not independent + assertFalse(isIndependent( + info.getChange(openInOpenTask.mRemoteToken.toWindowContainerToken()), info)); + + // Non-root changes are not independent + assertFalse(isIndependent( + info.getChange(changeInChangeTask.mRemoteToken.toWindowContainerToken()), info)); + + // open/close within a change are independent + assertTrue(isIndependent( + info.getChange(openInChangeTask.mRemoteToken.toWindowContainerToken()), info)); + } + /** Fill the change map with all the parents of top. Change maps are usually fully populated */ private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes, WindowContainer top) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index d1d0ac68017a..8b4e94724e6c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -24,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -49,7 +51,9 @@ import android.view.Gravity; import android.view.InsetsState; import android.view.RoundedCorners; import android.view.Surface; +import android.view.SurfaceControl; import android.view.WindowManager; +import android.window.ITransitionPlayer; import androidx.test.filters.SmallTest; @@ -135,7 +139,8 @@ public class WallpaperControllerTests extends WindowTestsBase { int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight)); // Check that the wallpaper is correctly scaled - assertEquals(new Rect(0, 0, expectedWidth, displayHeight), wallpaperWindow.getFrame()); + assertEquals(expectedWidth, wallpaperWindow.getFrame().width()); + assertEquals(displayHeight, wallpaperWindow.getFrame().height()); Rect portraitFrame = wallpaperWindow.getFrame(); // Rotate the display @@ -297,6 +302,46 @@ public class WallpaperControllerTests extends WindowTestsBase { assertFalse(mAppWindow.mActivityRecord.hasFixedRotationTransform()); } + @Test + public void testWallpaperTokenVisibility() { + final DisplayContent dc = mWm.mRoot.getDefaultDisplay(); + final WallpaperWindowToken token = new WallpaperWindowToken(mWm, mock(IBinder.class), + true, dc, true /* ownerCanManageAppTokens */); + final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, token, + "wallpaperWindow"); + wallpaperWindow.setHasSurface(true); + + // Set-up mock shell transitions + final IBinder mockBinder = mock(IBinder.class); + final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class); + doReturn(mockBinder).when(mockPlayer).asBinder(); + mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer); + + Transition transit = + mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN); + + // wallpaper windows are immediately visible when set to visible even during a transition + token.setVisibility(true); + assertTrue(wallpaperWindow.isVisible()); + assertTrue(token.isVisibleRequested()); + assertTrue(token.isVisible()); + mWm.mAtmService.getTransitionController().abort(transit); + + // In a transition, setting invisible should ONLY set requestedVisible false; otherwise + // wallpaper should remain "visible" until transition is over. + transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_CLOSE); + transit.start(); + token.setVisibility(false); + assertTrue(wallpaperWindow.isVisible()); + assertFalse(token.isVisibleRequested()); + assertTrue(token.isVisible()); + + transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class)); + transit.finishTransition(); + assertFalse(wallpaperWindow.isVisible()); + assertFalse(token.isVisible()); + } + private WindowState createWallpaperTargetWindow(DisplayContent dc) { final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) .setTask(dc.getDefaultTaskDisplayArea().getRootHomeTask()) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 99c96bd0de1b..bbb885eb0dd0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -984,6 +984,22 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + public void testFreezeInsets() { + final Task stack = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity = createActivityRecord(mDisplayContent, stack); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + + // Set visibility to false, verify the main window of the task will be set the frozen + // insets state immediately. + activity.setVisibility(false); + assertNotNull(win.getFrozenInsetsState()); + + // Now make it visible again, verify that the insets are immediately unfrozen. + activity.setVisibility(true); + assertNull(win.getFrozenInsetsState()); + } + + @Test public void testFreezeInsetsStateWhenAppTransition() { final Task stack = createTaskStackOnDisplay(mDisplayContent); final Task task = createTaskInStack(stack, 0 /* userId */); @@ -996,15 +1012,20 @@ public class WindowContainerTests extends WindowTestsBase { sources.add(activity); // Simulate the task applying the exit transition, verify the main window of the task - // will be set the frozen insets state. + // will be set the frozen insets state before the animation starts + activity.setVisibility(false); task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, false /* isVoiceInteraction */, sources); verify(win).freezeInsetsState(); - // Simulate the task transition finished, verify the frozen insets state of the window - // will be reset. + // Simulate the task transition finished. + activity.commitVisibility(false, false); task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, task.mSurfaceAnimator.getAnimation()); + + // Now make it visible again, verify that the insets are immediately unfrozen even before + // transition starts. + activity.setVisibility(true); verify(win).clearFrozenInsetsState(); } 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 c13d6b19bf1d..1f38f463a7d3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -245,6 +245,9 @@ class WindowTestsBase extends SystemServiceTestsBase { private WindowToken createWindowToken( DisplayContent dc, int windowingMode, int activityType, int type) { + if (type == TYPE_WALLPAPER) { + return createWallpaperToken(dc); + } if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) { return createTestWindowToken(type, dc); } @@ -252,6 +255,11 @@ class WindowTestsBase extends SystemServiceTestsBase { return createActivityRecord(dc, windowingMode, activityType); } + private WindowToken createWallpaperToken(DisplayContent dc) { + return new WallpaperWindowToken(mWm, mock(IBinder.class), true /* explicit */, dc, + true /* ownerCanManageAppTokens */); + } + WindowState createAppWindow(Task task, int type, String name) { final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent()); task.addChild(activity, 0); @@ -538,9 +546,8 @@ class WindowTestsBase extends SystemServiceTestsBase { /** Creates a {@link DisplayContent} and adds it to the system. */ private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy) { - final DisplayContent display = + final DisplayContent dc = new TestDisplayContent.Builder(mAtm, info).build(); - final DisplayContent dc = display.mDisplayContent; // this display can show IME. dc.mWmService.mDisplayWindowSettings.setDisplayImePolicy(dc, imePolicy); return dc; diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java index 6aadd23d211f..8874e0afd716 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerService.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java @@ -20,19 +20,29 @@ import static android.Manifest.permission.MANAGE_UI_TRANSLATION; import static android.content.Context.TRANSLATION_MANAGER_SERVICE; import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.translation.ITranslationManager; import android.view.translation.TranslationSpec; import android.view.translation.UiTranslationManager.UiTranslationState; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.List; /** @@ -48,6 +58,8 @@ public final class TranslationManagerService private static final String TAG = "TranslationManagerService"; + private static final int MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS = 2 * 60_000; // 2 minutes + public TranslationManagerService(Context context) { // TODO: Discuss the disallow policy super(context, new FrameworkResourcesServiceNameResolver(context, @@ -60,19 +72,82 @@ public final class TranslationManagerService return new TranslationManagerServiceImpl(this, mLock, resolvedUserId, disabled); } + @Override + protected void enforceCallingPermissionForManagement() { + getContext().enforceCallingPermission(MANAGE_UI_TRANSLATION, TAG); + } + + @Override + protected int getMaximumTemporaryServiceDurationMs() { + return MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS; + } + + @Override + protected void dumpLocked(String prefix, PrintWriter pw) { + super.dumpLocked(prefix, pw); + } + private void enforceCallerHasPermission(String permission) { final String msg = "Permission Denial from pid =" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " doesn't hold " + permission; getContext().enforceCallingPermission(permission, msg); } + /** True if the currently set handler service is not overridden by the shell. */ + @GuardedBy("mLock") + private boolean isDefaultServiceLocked(int userId) { + final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId); + if (defaultServiceName == null) { + return false; + } + + final String currentServiceName = mServiceNameResolver.getServiceName(userId); + return defaultServiceName.equals(currentServiceName); + } + + /** True if the caller of the api is the same app which hosts the TranslationService. */ + @GuardedBy("mLock") + private boolean isCalledByServiceAppLocked(int userId, @NonNull String methodName) { + final int callingUid = Binder.getCallingUid(); + + final String serviceName = mServiceNameResolver.getServiceName(userId); + if (serviceName == null) { + Slog.e(TAG, methodName + ": called by UID " + callingUid + + ", but there's no service set for user " + userId); + return false; + } + + final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); + if (serviceComponent == null) { + Slog.w(TAG, methodName + ": invalid service name: " + serviceName); + return false; + } + + final String servicePackageName = serviceComponent.getPackageName(); + final PackageManager pm = getContext().getPackageManager(); + final int serviceUid; + try { + serviceUid = pm.getPackageUidAsUser(servicePackageName, userId); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, methodName + ": could not verify UID for " + serviceName); + return false; + } + if (callingUid != serviceUid) { + Slog.e(TAG, methodName + ": called by UID " + callingUid + ", but service UID is " + + serviceUid); + return false; + } + return true; + } + final class TranslationManagerServiceStub extends ITranslationManager.Stub { @Override public void getSupportedLocales(IResultReceiver receiver, int userId) throws RemoteException { synchronized (mLock) { final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { + if (service != null && (isDefaultServiceLocked(userId) + || isCalledByServiceAppLocked(userId, "getSupportedLocales"))) { service.getSupportedLocalesLocked(receiver); } else { Slog.v(TAG, "getSupportedLocales(): no service for " + userId); @@ -86,7 +161,8 @@ public final class TranslationManagerService int sessionId, IResultReceiver receiver, int userId) throws RemoteException { synchronized (mLock) { final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { + if (service != null && (isDefaultServiceLocked(userId) + || isCalledByServiceAppLocked(userId, "onSessionCreated"))) { service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver); } else { Slog.v(TAG, "onSessionCreated(): no service for " + userId); @@ -96,18 +172,58 @@ public final class TranslationManagerService } @Override - public void updateUiTranslationState(@UiTranslationState int state, + public void updateUiTranslationStateByTaskId(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, int taskId, int userId) { + // deprecated enforceCallerHasPermission(MANAGE_UI_TRANSLATION); synchronized (mLock) { final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { - service.updateUiTranslationState(state, sourceSpec, destSpec, viewIds, + if (service != null && (isDefaultServiceLocked(userId) + || isCalledByServiceAppLocked(userId, + "updateUiTranslationStateByTaskId"))) { + service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds, taskId); } } } + + @Override + public void updateUiTranslationState(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, + IBinder token, int taskId, int userId) { + enforceCallerHasPermission(MANAGE_UI_TRANSLATION); + synchronized (mLock) { + final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null && (isDefaultServiceLocked(userId) + || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) { + service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds, + token, taskId); + } + } + } + + /** + * Dump the service state into the given stream. You run "adb shell dumpsys translation". + */ + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (mLock) { + dumpLocked("", pw); + } + } + + @Override + public void onShellCommand(@Nullable FileDescriptor in, + @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, + @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new TranslationManagerServiceShellCommand( + TranslationManagerService.this).exec(this, in, out, err, args, callback, + resultReceiver); + } } @Override // from SystemService diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index 38be85c92197..ab6ac12c90fa 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.IBinder; import android.os.RemoteException; import android.service.translation.TranslationServiceInfo; import android.util.Slog; @@ -133,18 +134,40 @@ final class TranslationManagerServiceImpl extends } @GuardedBy("mLock") - public void updateUiTranslationState(@UiTranslationState int state, + public void updateUiTranslationStateLocked(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, int taskId) { - // TODO(b/177394471): use taskId as a temporary solution. The solution may use a token to - // content capture manager service find the activitytoken. Then we can use this - // activitytoken to find the activity to callback. But we need to change cc API so use - // temporary solution. - final ActivityTokens tokens = mActivityTaskManagerInternal.getTopActivityForTask(taskId); - if (tokens == null) { + // deprecated + final ActivityTokens taskTopActivityTokens = + mActivityTaskManagerInternal.getTopActivityForTask(taskId); + if (taskTopActivityTokens == null) { Slog.w(TAG, "Unknown activity to query for update translation state."); return; } + updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec, + viewIds); + } + + @GuardedBy("mLock") + public void updateUiTranslationStateLocked(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, + IBinder token, int taskId) { + // Get top activity for a given task id + final ActivityTokens taskTopActivityTokens = + mActivityTaskManagerInternal.getTopActivityForTask(taskId); + if (taskTopActivityTokens == null + || taskTopActivityTokens.getShareableActivityToken() != token) { + Slog.w(TAG, "Unknown activity or it was finished to query for update " + + "translation state for token=" + token + " taskId=" + taskId); + return; + } + updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec, + viewIds); + } + + private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens, + @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, + List<AutofillId> viewIds) { try { tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state, sourceSpec, destSpec, viewIds); diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceShellCommand.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceShellCommand.java new file mode 100644 index 000000000000..ba1b3908a91e --- /dev/null +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceShellCommand.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.translation; + +import android.os.ShellCommand; + +import java.io.PrintWriter; + +/** Handles adb shell commands send to TranslationManagerService. */ +public class TranslationManagerServiceShellCommand extends ShellCommand { + private final TranslationManagerService mService; + + TranslationManagerServiceShellCommand(TranslationManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + if ("set".equals(cmd)) { + return requestSet(pw); + } + return handleDefaultCommands(cmd); + } + + private int requestSet(PrintWriter pw) { + final String what = getNextArgRequired(); + if ("temporary-service".equals(what)) { + return setTemporaryService(pw); + } + pw.println("Invalid set: " + what); + return -1; + } + + private int setTemporaryService(PrintWriter pw) { + final int userId = Integer.parseInt(getNextArgRequired()); + final String serviceName = getNextArg(); + if (serviceName == null) { + mService.resetTemporaryService(userId); + return 0; + } + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryService(userId, serviceName, duration); + pw.println("TranslationService temporarily set to " + serviceName + " for " + + duration + "ms"); + return 0; + } + + @Override + public void onHelp() { + try (PrintWriter pw = getOutPrintWriter();) { + pw.println("Translation Service (translation) commands:"); + pw.println(" help"); + pw.println(" Prints this help text."); + pw.println(""); + pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implementation."); + pw.println(" To reset, call with just the USER_ID argument."); + pw.println(""); + } + } +} diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index b1e6683f0486..f35b9e2ce0ed 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -303,7 +303,9 @@ class UserUsageStatsService { // FLUSH_TO_DISK is a private event. && event.mEventType != Event.FLUSH_TO_DISK // DEVICE_SHUTDOWN is added to event list after reboot. - && event.mEventType != Event.DEVICE_SHUTDOWN) { + && event.mEventType != Event.DEVICE_SHUTDOWN + // We aren't interested in every instance of the APP_COMPONENT_USED event. + && event.mEventType != Event.APP_COMPONENT_USED) { currentDailyStats.addEvent(event); } @@ -1176,6 +1178,8 @@ class UserUsageStatsService { return "USER_STOPPED"; case Event.LOCUS_ID_SET: return "LOCUS_ID_SET"; + case Event.APP_COMPONENT_USED: + return "APP_COMPONENT_USED"; default: return "UNKNOWN_TYPE_" + eventType; } diff --git a/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl b/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl new file mode 100644 index 000000000000..685fe9c927b1 --- /dev/null +++ b/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl @@ -0,0 +1,22 @@ +/* + * 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.telecom; + +/** + * {@hide} + */ +parcelable BluetoothCallQualityReport; diff --git a/telecomm/java/android/telecom/BluetoothCallQualityReport.java b/telecomm/java/android/telecom/BluetoothCallQualityReport.java index 10339a818205..8703d84831ff 100644 --- a/telecomm/java/android/telecom/BluetoothCallQualityReport.java +++ b/telecomm/java/android/telecom/BluetoothCallQualityReport.java @@ -24,6 +24,8 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * This class represents the quality report that bluetooth framework sends * whenever there's a bad voice quality is detected from their side. @@ -145,6 +147,26 @@ public final class BluetoothCallQualityReport implements Parcelable { } }; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BluetoothCallQualityReport that = (BluetoothCallQualityReport) o; + return mSentTimestampMillis == that.mSentTimestampMillis + && mChoppyVoice == that.mChoppyVoice && mRssiDbm == that.mRssiDbm + && mSnrDb == that.mSnrDb + && mRetransmittedPacketsCount == that.mRetransmittedPacketsCount + && mPacketsNotReceivedCount == that.mPacketsNotReceivedCount + && mNegativeAcknowledgementCount == that.mNegativeAcknowledgementCount; + } + + @Override + public int hashCode() { + return Objects.hash(mSentTimestampMillis, mChoppyVoice, mRssiDbm, mSnrDb, + mRetransmittedPacketsCount, mPacketsNotReceivedCount, + mNegativeAcknowledgementCount); + } + /** * Builder class for {@link ConnectionRequest} */ diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 044ea80cba4b..2a5ddfd891b1 100755 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -267,6 +267,64 @@ public final class Call { public static final String EVENT_HANDOVER_FAILED = "android.telecom.event.HANDOVER_FAILED"; + /** + * Event reported from the Telecom stack to report an in-call diagnostic message which the + * dialer app may opt to display to the user. A diagnostic message is used to communicate + * scenarios the device has detected which may impact the quality of the ongoing call. + * <p> + * For example a problem with a bluetooth headset may generate a recommendation for the user to + * try using the speakerphone instead, or if the device detects it has entered a poor service + * area, the user might be warned so that they can finish their call prior to it dropping. + * <p> + * A diagnostic message is considered persistent in nature. When the user enters a poor service + * area, for example, the accompanying diagnostic message persists until they leave the area + * of poor service. Each message is accompanied with a {@link #EXTRA_DIAGNOSTIC_MESSAGE_ID} + * which uniquely identifies the diagnostic condition being reported. The framework raises a + * call event of type {@link #EVENT_CLEAR_DIAGNOSTIC_MESSAGE} when the condition reported has + * been cleared. The dialer app should display the diagnostic message until it is cleared. + * If multiple diagnostic messages are sent with different IDs (which have not yet been cleared) + * the dialer app should prioritize the most recently received message, but still provide the + * user with a means to review past messages. + * <p> + * The text of the message is found in {@link #EXTRA_DIAGNOSTIC_MESSAGE} in the form of a human + * readable {@link CharSequence} which is intended for display in the call UX. + * <p> + * The telecom framework audibly notifies the user of the presence of a diagnostic message, so + * the dialer app needs only to concern itself with visually displaying the message. + * <p> + * The dialer app receives this event via + * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}. + */ + public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = + "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE"; + + /** + * Event reported from the telecom framework when a diagnostic message previously raised with + * {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE} has cleared and is no longer pertinent. + * <p> + * The {@link #EXTRA_DIAGNOSTIC_MESSAGE_ID} indicates the diagnostic message which has been + * cleared. + * <p> + * The dialer app receives this event via + * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}. + */ + public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE = + "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE"; + + /** + * Integer extra representing a message ID for a message posted via + * {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE}. Used to ensure that the dialer app knows when + * the message in question has cleared via {@link #EVENT_CLEAR_DIAGNOSTIC_MESSAGE}. + */ + public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = + "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID"; + + /** + * {@link CharSequence} extra used with {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE}. This is the + * diagnostic message the dialer app should display. + */ + public static final String EXTRA_DIAGNOSTIC_MESSAGE = + "android.telecom.extra.DIAGNOSTIC_MESSAGE"; /** * Reject reason used with {@link #reject(int)} to indicate that the user is rejecting this diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java new file mode 100644 index 000000000000..201c5db74e16 --- /dev/null +++ b/telecomm/java/android/telecom/CallDiagnosticService.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; + +import com.android.internal.telecom.ICallDiagnosticService; +import com.android.internal.telecom.ICallDiagnosticServiceAdapter; + +import java.util.Map; + +/** + * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the + * {@code call_diagnostic_service_package_name} key in the + * {@code packages/services/Telecomm/res/values/config.xml} file. An OEM can use this API to help + * provide more actionable information about calling issues the user encounters during and after + * a call. + * + * <h1>Manifest Declaration</h1> + * The following is an example of how to declare the service entry in the + * {@link CallDiagnosticService} manifest file: + * <pre> + * {@code + * <service android:name="your.package.YourCallDiagnosticServiceImplementation" + * android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"> + * <intent-filter> + * <action android:name="android.telecom.CallDiagnosticService"/> + * </intent-filter> + * </service> + * } + * </pre> + * @hide + */ +@SystemApi +public abstract class CallDiagnosticService extends Service { + + /** + * Binder stub implementation which handles incoming requests from Telecom. + */ + private final class CallDiagnosticServiceBinder extends ICallDiagnosticService.Stub { + + @Override + public void setAdapter(ICallDiagnosticServiceAdapter adapter) throws RemoteException { + handleSetAdapter(adapter); + } + + @Override + public void initializeDiagnosticCall(ParcelableCall call) throws RemoteException { + handleCallAdded(call); + } + + @Override + public void updateCall(ParcelableCall call) throws RemoteException { + handleCallUpdated(call); + } + + @Override + public void removeDiagnosticCall(String callId) throws RemoteException { + handleCallRemoved(callId); + } + + @Override + public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException { + onCallAudioStateChanged(callAudioState); + } + + @Override + public void receiveDeviceToDeviceMessage(String callId, int message, int value) { + handleReceivedD2DMessage(callId, message, value); + } + + @Override + public void receiveBluetoothCallQualityReport(BluetoothCallQualityReport qualityReport) + throws RemoteException { + handleBluetoothCallQualityReport(qualityReport); + } + } + + /** + * Listens to events raised by a {@link DiagnosticCall}. + */ + private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener = + new android.telecom.DiagnosticCall.Listener() { + + @Override + public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall, + @DiagnosticCall.MessageType int message, int value) { + handleSendDeviceToDeviceMessage(diagnosticCall, message, value); + } + + @Override + public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, + CharSequence message) { + handleDisplayDiagnosticMessage(diagnosticCall, messageId, message); + } + + @Override + public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) { + handleClearDiagnosticMessage(diagnosticCall, messageId); + } + }; + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService"; + + /** + * 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 ICallDiagnosticServiceAdapter mAdapter; + + @Nullable + @Override + public IBinder onBind(@NonNull Intent intent) { + Log.i(this, "onBind!"); + return new CallDiagnosticServiceBinder(); + } + + /** + * 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 + * used for the lifespan of this call. + * + * @param call The details of the new call. + * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService} + * provides to be used for the lifespan of the call. + * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned. + */ + public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@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. + * @param call The diagnostic call which is no longer tracked by Telecom. + */ + public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call); + + /** + * Telecom calls this method when the audio routing or available audio route information + * changes. + * <p> + * Audio state is common to all calls. + * + * @param audioState The new audio state. + */ + public abstract void onCallAudioStateChanged( + @NonNull CallAudioState audioState); + + /** + * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the + * bluetooth stack. + * @param qualityReport the {@link BluetoothCallQualityReport}. + */ + public abstract void onBluetoothCallQualityReportReceived( + @NonNull BluetoothCallQualityReport qualityReport); + + /** + * Handles a request from Telecom to set the adapater used to communicate back to Telecom. + * @param adapter + */ + private void handleSetAdapter(@NonNull ICallDiagnosticServiceAdapter adapter) { + mAdapter = adapter; + } + + /** + * Handles a request from Telecom to add a new call. + * @param parcelableCall + */ + private void handleCallAdded(@NonNull ParcelableCall parcelableCall) { + 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."); + } + diagnosticCall.setListener(mDiagnosticCallListener); + diagnosticCall.setCallId(telecomCallId); + mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall); + } + + /** + * 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)}. + * @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); + } + + /** + * Handles a request from Telecom to remove an existing call. + * @param telecomCallId + */ + private void handleCallRemoved(@NonNull String telecomCallId) { + Log.i(this, "handleCallRemoved: callId=%s - removed", telecomCallId); + + 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); + } + } + + /** + * Handles an incoming device to device message received from Telecom. Notifies the + * {@link DiagnosticCall} via {@link DiagnosticCall#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); + } + + /** + * Handles an incoming bluetooth call quality report from Telecom. Notifies via + * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived( + * BluetoothCallQualityReport)}. + * @param qualityReport The bluetooth call quality remote. + */ + private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport + qualityReport) { + Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport); + 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 + * @param message + * @param value + */ + private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall, + int message, int value) { + String callId = diagnosticCall.getCallId(); + try { + mAdapter.sendDeviceToDeviceMessage(callId, message, value); + Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message, + value); + } catch (RemoteException e) { + Log.w(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d failed %s", + callId, message, value, e); + } + } + + /** + * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message. + * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}. + * @param diagnosticCall + * @param messageId + * @param message + */ + private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, + CharSequence message) { + String callId = diagnosticCall.getCallId(); + try { + mAdapter.displayDiagnosticMessage(callId, messageId, message); + Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId, + message); + } catch (RemoteException e) { + Log.w(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s failed %s", + callId, messageId, message, e); + } + } + + /** + * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic + * message. + * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}. + * @param diagnosticCall + * @param messageId + */ + private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) { + String callId = diagnosticCall.getCallId(); + try { + mAdapter.clearDiagnosticMessage(callId, messageId); + Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId); + } catch (RemoteException e) { + Log.w(this, "handleClearDiagnosticMessage: call=%s; msg=%d failed %s", + callId, messageId, e); + } + } +} diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 7c6253ce933a..335857af8883 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -938,6 +938,46 @@ public abstract class Connection extends Conferenceable { public static final String EVENT_RTT_AUDIO_INDICATION_CHANGED = "android.telecom.event.RTT_AUDIO_INDICATION_CHANGED"; + /** + * Connection event used to signal between the telephony {@link ConnectionService} and Telecom + * when device to device messages are sent/received. + * <p> + * Device to device messages originating from the network are sent by telephony using + * {@link Connection#sendConnectionEvent(String, Bundle)} and are routed up to any active + * {@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 + * via {@link Connection#onCallEvent(String, Bundle)}. The telephony stack will relay the + * message to the other device. + * @hide + */ + @SystemApi + public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE = + "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE"; + + /** + * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device + * message type. + * + * See {@link DiagnosticCall} for more information. + * @hide + */ + @SystemApi + public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE = + "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE"; + + /** + * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device + * message value. + * <p> + * See {@link DiagnosticCall} for more information. + * @hide + */ + @SystemApi + public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE = + "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE"; + // Flag controlling whether PII is emitted into the logs private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); diff --git a/telecomm/java/android/telecom/DiagnosticCall.java b/telecomm/java/android/telecom/DiagnosticCall.java new file mode 100644 index 000000000000..a4952899eb46 --- /dev/null +++ b/telecomm/java/android/telecom/DiagnosticCall.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +/** + * 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). + * @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); + } +} diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index 2a4fdcb1475d..922eddb6ac3e 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -522,7 +522,7 @@ public class Log { return ""; } return Arrays.stream(packageName.split("\\.")) - .map(s -> s.substring(0,1)) + .map(s -> s.length() == 0 ? "" : s.substring(0, 1)) .collect(Collectors.joining("")); } } diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl new file mode 100644 index 000000000000..65b4d19b3d9b --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl @@ -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.internal.telecom; + +import android.telecom.BluetoothCallQualityReport; +import android.telecom.CallAudioState; +import android.telecom.ParcelableCall; +import com.android.internal.telecom.ICallDiagnosticServiceAdapter; + +/** + * Internal remote interface for a call diagnostic service. + * @see android.telecom.CallDiagnosticService + * @hide + */ +oneway interface ICallDiagnosticService { + void setAdapter(in ICallDiagnosticServiceAdapter adapter); + void initializeDiagnosticCall(in ParcelableCall call); + void updateCall(in ParcelableCall call); + void updateCallAudioState(in CallAudioState callAudioState); + void removeDiagnosticCall(in String callId); + void receiveDeviceToDeviceMessage(in String callId, int message, int value); + void receiveBluetoothCallQualityReport(in BluetoothCallQualityReport qualityReport); +} diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl new file mode 100644 index 000000000000..92eec2a95430 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telecom; + +import android.telecom.CallAudioState; +import android.telecom.ParcelableCall; + +/** + * Remote interface for messages from the CallDiagnosticService to the platform. + * @see android.telecom.CallDiagnosticService + * @hide + */ +oneway interface ICallDiagnosticServiceAdapter { + void displayDiagnosticMessage(in String callId, int messageId, in CharSequence message); + void clearDiagnosticMessage(in String callId, int messageId); + void sendDeviceToDeviceMessage(in String callId, int message, int value); + void overrideDisconnectMessage(in String callId, in CharSequence message); +} diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index eb106b50f69d..78283fa73514 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -353,4 +353,8 @@ interface ITelecomService { */ void setTestDefaultDialer(in String packageName); + /** + * @see TelecomServiceImpl#setTestCallDiagnosticService + */ + void setTestCallDiagnosticService(in String packageName); } diff --git a/telephony/common/com/android/internal/telephony/SipMessageParsingUtils.java b/telephony/common/com/android/internal/telephony/SipMessageParsingUtils.java index c7e7cd5ec64e..179248dd2cf9 100644 --- a/telephony/common/com/android/internal/telephony/SipMessageParsingUtils.java +++ b/telephony/common/com/android/internal/telephony/SipMessageParsingUtils.java @@ -65,6 +65,11 @@ public class SipMessageParsingUtils { // compact form of the via header key private static final String VIA_SIP_HEADER_KEY_COMPACT = "v"; + // call-id header key + private static final String CALL_ID_SIP_HEADER_KEY = "call-id"; + // compact form of the call-id header key + private static final String CALL_ID_SIP_HEADER_KEY_COMPACT = "i"; + /** * @return true if the SIP message start line is considered a request (based on known request * methods). @@ -124,6 +129,17 @@ public class SipMessageParsingUtils { return null; } + /** + * Return the call-id header key's associated value. + * @param headerString The string containing the headers of the SIP message. + */ + public static String getCallId(String headerString) { + // search for the call-Id header, there should only be one in the header. + List<Pair<String, String>> headers = parseHeaders(headerString, true, + CALL_ID_SIP_HEADER_KEY, CALL_ID_SIP_HEADER_KEY_COMPACT); + return !headers.isEmpty() ? headers.get(0).second : null; + } + private static String[] splitStartLineAndVerify(String startLine) { String[] splitLine = startLine.split(" "); if (isStartLineMalformed(splitLine)) return null; diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 706e3cb93a0f..a78f81331c8c 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -371,6 +371,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * Get the 5G NR connection state. * * @return the 5G NR connection state. + * @hide */ public @NRState int getNrState() { return mNrState; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 1473b7a8873d..cf4e6779b363 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -891,6 +891,14 @@ public class SubscriptionManager { public static final String PROFILE_CLASS = SimInfo.COLUMN_PROFILE_CLASS; /** + * TelephonyProvider column name for VoIMS opt-in status. + * + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String VOIMS_OPT_IN_STATUS = SimInfo.COLUMN_VOIMS_OPT_IN_STATUS; + + /** * Profile class of the subscription * @hide */ diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java index 5b5570b27a9b..2f89bfb60d3d 100644 --- a/telephony/java/android/telephony/TelephonyDisplayInfo.java +++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java @@ -30,8 +30,8 @@ import java.util.Objects; * necessarily a precise or accurate representation of the current state and should be treated * accordingly. * To be notified of changes in TelephonyDisplayInfo, use - * {@link TelephonyManager#registerPhoneStateListener} with a {@link PhoneStateListener} - * that implements {@link PhoneStateListener.DisplayInfoChangedListener}. + * {@link TelephonyManager#registerTelephonyCallback} with a {@link TelephonyCallback} + * that implements {@link TelephonyCallback.DisplayInfoListener}. * Override the onDisplayInfoChanged() method to handle the broadcast. */ public final class TelephonyDisplayInfo implements Parcelable { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 61e809b55031..c48bd211fac2 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5949,25 +5949,20 @@ public class TelephonyManager { * @param events The telephony state(s) of interest to the listener, * as a bitwise-OR combination of {@link PhoneStateListener} * LISTEN_ flags. - * @deprecated Use {@link #registerPhoneStateListener(Executor, PhoneStateListener)}. + * @deprecated Use {@link #registerTelephonyCallback(Executor, TelephonyCallback)}. */ @Deprecated public void listen(PhoneStateListener listener, int events) { - if (!listener.isExecutorSet()) { - throw new IllegalStateException("PhoneStateListener should be created on a thread " - + "with Looper.myLooper() != null"); - } - boolean notifyNow = getITelephony() != null; - mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class); - if (mTelephonyRegistryMgr != null) { - if (events != PhoneStateListener.LISTEN_NONE) { - mTelephonyRegistryMgr.registerPhoneStateListenerWithEvents(mSubId, - getOpPackageName(), getAttributionTag(), listener, events, notifyNow); - } else { - unregisterPhoneStateListener(listener); - } + if (mContext == null) return; + boolean notifyNow = (getITelephony() != null); + TelephonyRegistryManager telephonyRegistry = + (TelephonyRegistryManager) + mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); + if (telephonyRegistry != null) { + telephonyRegistry.listenFromListener(mSubId, getOpPackageName(), + getAttributionTag(), listener, events, notifyNow); } else { - throw new IllegalStateException("telephony service is null."); + Rlog.w(TAG, "telephony registry not ready."); } } @@ -9129,18 +9124,11 @@ public class TelephonyManager { */ public static final int CALL_COMPOSER_STATUS_ON = 1; - /** - * Call composer status indicating that sending/receiving pictures is disabled. - * All other attachments are still enabled in this state. - */ - public static final int CALL_COMPOSER_STATUS_ON_NO_PICTURES = 2; - /** @hide */ @IntDef(prefix = {"CALL_COMPOSER_STATUS_"}, value = { CALL_COMPOSER_STATUS_ON, CALL_COMPOSER_STATUS_OFF, - CALL_COMPOSER_STATUS_ON_NO_PICTURES, }) public @interface CallComposerStatus {} @@ -9148,9 +9136,8 @@ public class TelephonyManager { * Set the user-set status for enriched calling with call composer. * * @param status user-set status for enriched calling with call composer; - * it must be any of {@link #CALL_COMPOSER_STATUS_ON} - * {@link #CALL_COMPOSER_STATUS_OFF}, - * or {@link #CALL_COMPOSER_STATUS_ON_NO_PICTURES} + * it must be either {@link #CALL_COMPOSER_STATUS_ON} or + * {@link #CALL_COMPOSER_STATUS_OFF}. * * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} @@ -9160,7 +9147,7 @@ public class TelephonyManager { */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(@CallComposerStatus int status) { - if (status > CALL_COMPOSER_STATUS_ON_NO_PICTURES + if (status > CALL_COMPOSER_STATUS_ON || status < CALL_COMPOSER_STATUS_OFF) { throw new IllegalArgumentException("requested status is invalid"); } @@ -9183,9 +9170,8 @@ public class TelephonyManager { * * @throws SecurityException if the caller does not have the permission. * - * @return the user-set status for enriched calling with call composer, any of - * {@link #CALL_COMPOSER_STATUS_ON}, {@link #CALL_COMPOSER_STATUS_OFF}, or - * {@link #CALL_COMPOSER_STATUS_ON_NO_PICTURES}. + * @return the user-set status for enriched calling with call composer, either of + * {@link #CALL_COMPOSER_STATUS_ON} or {@link #CALL_COMPOSER_STATUS_OFF}. */ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public @CallComposerStatus int getCallComposerStatus() { @@ -15058,66 +15044,69 @@ public class TelephonyManager { } /** - * Registers a listener object to receive notification of changes - * in specified telephony states. + * Registers a callback object to receive notification of changes in specified telephony states. * <p> - * To register a listener, pass a {@link PhoneStateListener} which implements + * To register a callback, pass a {@link TelephonyCallback} which implements * interfaces of events. For example, - * FakeServiceStateChangedListener extends {@link PhoneStateListener} implements - * {@link PhoneStateListener.ServiceStateChangedListener}. + * FakeServiceStateCallback extends {@link TelephonyCallback} implements + * {@link TelephonyCallback.ServiceStateListener}. * * At registration, and when a specified telephony state changes, the telephony manager invokes - * the appropriate callback method on the listener object and passes the current (updated) + * the appropriate callback method on the callback object and passes the current (updated) * values. * <p> * * If this TelephonyManager object has been created with {@link #createForSubscriptionId}, * applies to the given subId. Otherwise, applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds, - * pass a separate listener object to each TelephonyManager object created with + * {@link SubscriptionManager#getDefaultSubscriptionId()}. To register events for multiple + * subIds, pass a separate callback object to each TelephonyManager object created with * {@link #createForSubscriptionId}. * * Note: if you call this method while in the middle of a binder transaction, you <b>must</b> * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A * {@link SecurityException} will be thrown otherwise. * - * This API should be used sparingly -- large numbers of listeners will cause system - * instability. If a process has registered too many listeners without unregistering them, it - * may encounter an {@link IllegalStateException} when trying to register more listeners. + * This API should be used sparingly -- large numbers of callbacks will cause system + * instability. If a process has registered too many callbacks without unregistering them, it + * may encounter an {@link IllegalStateException} when trying to register more callbacks. * * @param executor The executor of where the callback will execute. - * @param listener The {@link PhoneStateListener} object to register. + * @param callback The {@link TelephonyCallback} object to register. */ - public void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor, - @NonNull PhoneStateListener listener) { - if (executor == null || listener == null) { - throw new IllegalArgumentException("PhoneStateListener and executor must be non-null"); + public void registerTelephonyCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull TelephonyCallback callback) { + if (executor == null || callback == null) { + throw new IllegalArgumentException("TelephonyCallback and executor must be non-null"); } mTelephonyRegistryMgr = (TelephonyRegistryManager) mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); if (mTelephonyRegistryMgr != null) { - mTelephonyRegistryMgr.registerPhoneStateListener(executor, mSubId, - getOpPackageName(), getAttributionTag(), listener, getITelephony() != null); + mTelephonyRegistryMgr.registerTelephonyCallback(executor, mSubId, getOpPackageName(), + getAttributionTag(), callback, getITelephony() != null); } else { throw new IllegalStateException("telephony service is null."); } } /** - * Unregister an existing {@link PhoneStateListener}. + * Unregister an existing {@link TelephonyCallback}. * - * @param listener The {@link PhoneStateListener} object to unregister. + * @param callback The {@link TelephonyCallback} object to unregister. */ - public void unregisterPhoneStateListener(@NonNull PhoneStateListener listener) { + public void unregisterTelephonyCallback(@NonNull TelephonyCallback callback) { if (mContext == null) { throw new IllegalStateException("telephony service is null."); } + if (callback.callback == null) { + return; + } + mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class); if (mTelephonyRegistryMgr != null) { - mTelephonyRegistryMgr.unregisterPhoneStateListener(mSubId, getOpPackageName(), - getAttributionTag(), listener, getITelephony() != null); + mTelephonyRegistryMgr.unregisterTelephonyCallback(mSubId, getOpPackageName(), + getAttributionTag(), callback, getITelephony() != null); } else { throw new IllegalStateException("telephony service is null."); } diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 6fda2e12fffd..0ab679f79b29 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -866,6 +866,19 @@ public class ProvisioningManager { public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; /** + * An integer key representing the voice over IMS opt-in provisioning status for the + * associated subscription. Determines whether the user can see for voice services over + * IMS. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoIMS provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable VoIMS provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + * @hide + */ + public static final int KEY_VOIMS_OPT_IN_STATUS = 68; + + /** * Callback for IMS provisioning changes. */ public static class Callback { diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java index bdf628b4d339..cedf48b0b8e1 100644 --- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java +++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java @@ -24,9 +24,14 @@ import android.net.Uri; import android.os.Build; 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; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -39,6 +44,8 @@ import java.util.List; @SystemApi public final class RcsContactPresenceTuple implements Parcelable { + private static final String LOG_TAG = "RcsContactPresenceTuple"; + /** * The service ID used to indicate that service discovery via presence is available. * <p> @@ -370,7 +377,8 @@ public final class RcsContactPresenceTuple implements Parcelable { } /** - * The optional SIP Contact URI associated with the PIDF tuple element. + * The optional SIP Contact URI associated with the PIDF tuple element if the network + * expects the user to use the URI instead of the contact URI to contact it. */ public @NonNull Builder setContactUri(@NonNull Uri contactUri) { mPresenceTuple.mContactUri = contactUri; @@ -381,8 +389,24 @@ public final class RcsContactPresenceTuple implements Parcelable { * 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; return this; } @@ -414,7 +438,7 @@ public final class RcsContactPresenceTuple implements Parcelable { } private Uri mContactUri; - private String mTimestamp; + private Instant mTimestamp; private @BasicStatus String mStatus; // The service information in the service-description element. @@ -433,7 +457,7 @@ public final class RcsContactPresenceTuple implements Parcelable { private RcsContactPresenceTuple(Parcel in) { mContactUri = in.readParcelable(Uri.class.getClassLoader()); - mTimestamp = in.readString(); + mTimestamp = convertStringFormatTimeToInstant(in.readString()); mStatus = in.readString(); mServiceId = in.readString(); mServiceVersion = in.readString(); @@ -444,7 +468,7 @@ public final class RcsContactPresenceTuple implements Parcelable { @Override public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mContactUri, flags); - out.writeString(mTimestamp); + out.writeString(convertInstantToStringFormat(mTimestamp)); out.writeString(mStatus); out.writeString(mServiceId); out.writeString(mServiceVersion); @@ -470,6 +494,26 @@ public final class RcsContactPresenceTuple implements Parcelable { } }; + // Convert the Instant to the string format + private String convertInstantToStringFormat(Instant instant) { + if (instant == null) { + return ""; + } + return instant.toString(); + } + + // Convert the time string format to Instant + private @Nullable Instant convertStringFormatTimeToInstant(String timestamp) { + if (TextUtils.isEmpty(timestamp)) { + return null; + } + try { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from); + } catch (DateTimeParseException e) { + return null; + } + } + /** @return the status of the tuple element. */ public @NonNull @BasicStatus String getStatus() { return mStatus; @@ -490,8 +534,16 @@ public final class RcsContactPresenceTuple implements Parcelable { return mContactUri; } - /** @return the timestamp element contained in the tuple if it exists */ + /** + * @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/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java index 9299fed1e27d..52d0f036788c 100644 --- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java +++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java @@ -340,6 +340,7 @@ public final class RcsContactUceCapability implements Parcelable { } /** + * Retrieve the contact URI requested by the applications. * @return the URI representing the contact associated with the capabilities. */ public @NonNull Uri getContactUri() { diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 09c07d3f203c..815c08d120c2 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -32,11 +32,12 @@ import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.aidl.IRcsUceControllerCallback; import android.telephony.ims.aidl.IRcsUcePublishStateCallback; -import android.telephony.ims.feature.RcsFeature; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -431,13 +432,15 @@ public class RcsUceAdapter { /** * The pending request has completed successfully due to all requested contacts information - * being delivered. + * being delivered. The callback {@link #onCapabilitiesReceived(List)} + * for each contacts is required to be called before {@link #onComplete} is called. */ void onComplete(); /** * The pending request has resulted in an error and may need to be retried, depending on the - * error code. + * error code. The callback {@link #onCapabilitiesReceived(List)} + * for each contacts is required to be called before {@link #onError} is called. * @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. @@ -484,7 +487,6 @@ public class RcsUceAdapter { * 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}) public void requestCapabilities(@NonNull List<Uri> contactNumbers, @@ -550,6 +552,94 @@ public class RcsUceAdapter { } /** + * 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}) + public void requestCapabilities(@NonNull Collection<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(), new ArrayList(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); + } + } + + /** * Ignore the device cache and perform a capability discovery for one contact, also called * "availability fetch." * <p> @@ -570,6 +660,10 @@ public class RcsUceAdapter { * {@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 diff --git a/telephony/java/android/telephony/ims/SipMessage.java b/telephony/java/android/telephony/ims/SipMessage.java index 9cfa640fce18..ad6d73c39962 100644 --- a/telephony/java/android/telephony/ims/SipMessage.java +++ b/telephony/java/android/telephony/ims/SipMessage.java @@ -19,6 +19,7 @@ package android.telephony.ims; import static java.nio.charset.StandardCharsets.UTF_8; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Build; import android.os.Parcel; @@ -46,6 +47,8 @@ public final class SipMessage implements Parcelable { private final String mStartLine; private final String mHeaderSection; private final byte[] mContent; + private final String mViaBranchParam; + private final String mCallIdParam; /** * Represents a partially encoded SIP message. @@ -63,6 +66,9 @@ public final class SipMessage implements Parcelable { mStartLine = startLine; mHeaderSection = headerSection; mContent = content; + + mViaBranchParam = SipMessageParsingUtils.getTransactionId(mHeaderSection); + mCallIdParam = SipMessageParsingUtils.getCallId(mHeaderSection); } /** @@ -73,6 +79,8 @@ public final class SipMessage implements Parcelable { mHeaderSection = source.readString(); mContent = new byte[source.readInt()]; source.readByteArray(mContent); + mViaBranchParam = source.readString(); + mCallIdParam = source.readString(); } /** @@ -97,6 +105,25 @@ public final class SipMessage implements Parcelable { return mContent; } + /** + * @return the branch parameter enclosed in the Via header key's value. See RFC 3261 section + * 20.42 for more information on the Via header. If {@code null}, then there was either no + * Via parameter found in this SIP message's headers or no branch parameter found in the + * Via header. + */ + public @Nullable String getViaBranchParameter() { + return mViaBranchParam; + } + + /** + * @return the value associated with the call-id header of this SIP message. See RFC 3261 + * section 20.8 for more information on the call-id header. If {@code null}, then there was no + * call-id header found in this SIP message's headers. + */ + public @Nullable String getCallIdParameter() { + return mCallIdParam; + } + @Override public int describeContents() { return 0; @@ -108,6 +135,8 @@ public final class SipMessage implements Parcelable { dest.writeString(mHeaderSection); dest.writeInt(mContent.length); dest.writeByteArray(mContent); + dest.writeString(mViaBranchParam); + dest.writeString(mCallIdParam); } public static final @NonNull Creator<SipMessage> CREATOR = new Creator<SipMessage>() { diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl index 5eee3890f1dc..1b5e5603ec66 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl @@ -47,4 +47,6 @@ interface IImsConfig { void removeRcsConfigCallback(IRcsConfigCallback c); void triggerRcsReconfiguration(); void setRcsClientConfiguration(in RcsClientConfiguration rcc); + void notifyIntImsConfigChanged(int item, int value); + void notifyStringImsConfigChanged(int item, String value); } diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java index 9d919015087d..739946be2e5b 100644 --- a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java +++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java @@ -31,8 +31,6 @@ import android.telephony.ims.stub.SipDelegate; import android.text.TextUtils; import android.util.Log; -import com.android.internal.telephony.SipMessageParsingUtils; - import java.util.ArrayList; import java.util.Set; import java.util.concurrent.Executor; @@ -188,7 +186,7 @@ public class SipDelegateAidlWrapper implements DelegateStateCallback, DelegateMe } private void notifyLocalMessageFailedToBeReceived(SipMessage m, int reason) { - String transactionId = SipMessageParsingUtils.getTransactionId(m.getHeaderSection()); + String transactionId = m.getViaBranchParameter(); if (TextUtils.isEmpty(transactionId)) { Log.w(LOG_TAG, "failure to parse SipMessage."); throw new IllegalArgumentException("Malformed SipMessage, can not determine " diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java index c877aca8ba96..3cd27264295c 100644 --- a/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java +++ b/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java @@ -32,8 +32,6 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import com.android.internal.telephony.SipMessageParsingUtils; - import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.Executor; @@ -268,7 +266,7 @@ public class SipDelegateConnectionAidlWrapper implements SipDelegateConnection, } private void notifyLocalMessageFailedToSend(SipMessage m, int reason) { - String transactionId = SipMessageParsingUtils.getTransactionId(m.getHeaderSection()); + String transactionId = m.getViaBranchParameter(); if (TextUtils.isEmpty(transactionId)) { Log.w(LOG_TAG, "sendMessage detected a malformed SipMessage and can not get a " + "transaction ID."); diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 34984e05e181..21aeb64bb417 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -258,6 +258,16 @@ public class ImsConfigImplBase { public void setRcsClientConfiguration(RcsClientConfiguration rcc) throws RemoteException { getImsConfigImpl().setRcsClientConfiguration(rcc); } + + @Override + public void notifyIntImsConfigChanged(int item, int value) throws RemoteException { + notifyImsConfigChanged(item, value); + } + + @Override + public void notifyStringImsConfigChanged(int item, String value) throws RemoteException { + notifyImsConfigChanged(item, value); + } } /** diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java index 908869beb607..00c91681d9ea 100644 --- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java +++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java @@ -31,6 +31,7 @@ import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -241,7 +242,7 @@ public class RcsCapabilityExchangeImplBase { /** * Notify the framework of the response to the SUBSCRIBE request from - * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)}. + * {@link #subscribeForCapabilities(Collection, SubscribeResponseCallback)}. * <p> * If the carrier network responds to the SUBSCRIBE request with a 2XX response, then the * framework will expect the IMS stack to call {@link #onNotifyCapabilitiesUpdate}, @@ -266,7 +267,7 @@ public class RcsCapabilityExchangeImplBase { /** * Notify the framework of the response to the SUBSCRIBE request from - * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)} that also + * {@link #subscribeForCapabilities(Collection, SubscribeResponseCallback)} that also * includes a reason provided in the “reason” header. See RFC3326 for more * information. * @@ -388,6 +389,7 @@ public class RcsCapabilityExchangeImplBase { * @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") @@ -403,6 +405,40 @@ public class RcsCapabilityExchangeImplBase { } /** + * 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. + */ + // executor used is defined in the constructor. + @SuppressLint("ExecutorRegistration") + public void subscribeForCapabilities(@NonNull Collection<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 capabilities of this device have been updated and should be published to the network. * <p> * If this operation succeeds, network response updates should be sent to the framework using diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 15d19a49ee56..541292a1e230 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -114,6 +114,7 @@ public class DctConstants { public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 54; public static final int EVENT_SIM_STATE_UPDATED = BASE + 55; public static final int EVENT_APN_UNTHROTTLED = BASE + 56; + public static final int EVENT_AIRPLANE_MODE_CHANGED = BASE + 57; /***** Constants *****/ diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index e87f3d9aec76..7e04baa5f82b 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1200,15 +1200,6 @@ interface ITelephony { void shutdownMobileRadios(); /** - * Set phone radio type and access technology. - * - * @param rafs an RadioAccessFamily array to indicate all phone's - * new radio access family. The length of RadioAccessFamily - * must equ]]al to phone count. - */ - void setRadioCapability(in RadioAccessFamily[] rafs); - - /** * Get phone radio type and access technology. * * @param phoneId which phone you want to get 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 386dafc590af..b61310aa4bd8 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 @@ -102,7 +102,7 @@ class OpenAppWarmTest(private val testSpec: FlickerTestParameter) { @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Presubmit + @FlakyTest @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() = testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 62ccb1a0b9db..6bf44920ee26 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -771,6 +771,24 @@ </intent-filter> </activity> + <activity android:name="StretchShaderActivity" + android:label="RenderEffect/Stretch" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + + <activity android:name="EdgeEffectStretchActivity" + android:label="RenderEffect/EdgeEffect stretch" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="TextActivity" android:label="Text/Simple Text" android:theme="@android:style/Theme.NoTitleBar" diff --git a/tests/HwAccelerationTest/res/layout/stretch_layout.xml b/tests/HwAccelerationTest/res/layout/stretch_layout.xml new file mode 100644 index 000000000000..df5f297da729 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/stretch_layout.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/scroll_view" + android:edgeEffectType="stretch" + android:layout_width="match_parent" + android:layout_height="match_parent" > + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <HorizontalScrollView + android:id="@+id/horizontal_scroll_view" + android:edgeEffectType="stretch" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + </LinearLayout> + </HorizontalScrollView> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + + </LinearLayout> +</ScrollView> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java new file mode 100644 index 000000000000..f0e6299f09e9 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.HorizontalScrollView; +import android.widget.ScrollView; + +public class EdgeEffectStretchActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.stretch_layout); + HorizontalScrollView hsv = findViewById(R.id.horizontal_scroll_view); + hsv.setStretchDistance(50f); + + ScrollView sv = findViewById(R.id.scroll_view); + sv.setStretchDistance(50f); + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java new file mode 100644 index 000000000000..9bd933afa07d --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.hwui; + +import android.annotation.Nullable; +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.RecordingCanvas; +import android.graphics.Rect; +import android.graphics.RenderEffect; +import android.graphics.RuntimeShader; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +public class StretchShaderActivity extends Activity { + + private static final float MAX_STRETCH_INTENSITY = 1.5f; + private static final float STRETCH_AFFECTED_DISTANCE = 1.0f; + + private float mScrollX = 0f; + private float mScrollY = 0f; + + private float mMaxStretchIntensity = MAX_STRETCH_INTENSITY; + private float mStretchAffectedDistance = STRETCH_AFFECTED_DISTANCE; + + private float mOverscrollX = 25f; + private float mOverscrollY = 25f; + + private RuntimeShader mRuntimeShader; + private ImageView mImageView; + private ImageView mTestImageView; + + private Bitmap mBitmap; + + private StretchDrawable mStretchDrawable = new StretchDrawable(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + mBitmap = ((BitmapDrawable) getDrawable(R.drawable.sunset1)).getBitmap(); + mRuntimeShader = new RuntimeShader(SKSL, false); + + BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, + Shader.TileMode.CLAMP); + mRuntimeShader.setInputShader("uContentTexture", bitmapShader); + + mImageView = new ImageView(this); + + mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader)); + mImageView.setImageDrawable(new ColorDrawable(Color.CYAN)); + + TextView overscrollXText = new TextView(this); + overscrollXText.setText("Overscroll X"); + + SeekBar overscrollXBar = new SeekBar(this); + overscrollXBar.setProgress(0); + overscrollXBar.setMin(-50); + overscrollXBar.setMax(50); + overscrollXBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mOverscrollX = progress; + overscrollXText.setText("Overscroll X: " + mOverscrollX); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView overscrollYText = new TextView(this); + overscrollYText.setText("Overscroll Y"); + + SeekBar overscrollYBar = new SeekBar(this); + overscrollYBar.setProgress(0); + overscrollYBar.setMin(-50); + overscrollYBar.setMax(50); + overscrollYBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mOverscrollY = progress; + overscrollYText.setText("Overscroll Y: " + mOverscrollY); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView scrollXText = new TextView(this); + scrollXText.setText("Scroll X"); + SeekBar scrollXSeekBar = new SeekBar(this); + scrollXSeekBar.setMin(0); + scrollXSeekBar.setMax(100); + scrollXSeekBar.setProgress(0); + scrollXSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mScrollX = (progress / 100f); + scrollXText.setText("Scroll X: " + mScrollY); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView scrollYText = new TextView(this); + scrollYText.setText("Scroll Y"); + SeekBar scrollYSeekBar = new SeekBar(this); + scrollYSeekBar.setMin(0); + scrollYSeekBar.setMax(100); + scrollYSeekBar.setProgress(0); + scrollYSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mScrollY = (progress / 100f); + scrollYText.setText("Scroll Y: " + mScrollY); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView stretchIntensityText = new TextView(this); + int stretchProgress = (int) (mMaxStretchIntensity * 100); + stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity); + SeekBar stretchIntensitySeekbar = new SeekBar(this); + stretchIntensitySeekbar.setProgress(stretchProgress); + stretchIntensitySeekbar.setMin(1); + stretchIntensitySeekbar.setMax((int) (MAX_STRETCH_INTENSITY * 100)); + stretchIntensitySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mMaxStretchIntensity = progress / 100f; + stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView stretchDistanceText = new TextView(this); + stretchDistanceText.setText("StretchDistance"); + SeekBar stretchDistanceSeekbar = new SeekBar(this); + stretchDistanceSeekbar.setMin(0); + stretchDistanceSeekbar.setProgress((int) (mStretchAffectedDistance * 100)); + stretchDistanceSeekbar.setMax(100); + stretchDistanceSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mStretchAffectedDistance = progress / 100f; + stretchDistanceText.setText("StretchDistance: " + mStretchAffectedDistance); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + + linearLayout.addView(mImageView, + new LinearLayout.LayoutParams( + mBitmap.getWidth(), + mBitmap.getHeight()) + ); + + linearLayout.addView(overscrollXText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + linearLayout.addView(overscrollXBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(overscrollYText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + linearLayout.addView(overscrollYBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(scrollXText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(scrollXSeekBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(scrollYText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(scrollYSeekBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(stretchIntensityText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(stretchIntensitySeekbar, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(stretchDistanceText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(stretchDistanceSeekbar, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + ImageView test = new ImageView(this); + mStretchDrawable.setBitmap(mBitmap); + test.setImageDrawable(mStretchDrawable); + + mTestImageView = test; + linearLayout.addView(test, + new LinearLayout.LayoutParams(mBitmap.getWidth(), mBitmap.getHeight())); + + setContentView(linearLayout); + + } + + @Override + protected void onResume() { + super.onResume(); + mImageView.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + updateShader(); + mImageView.getViewTreeObserver().removeOnPreDrawListener(this); + return false; + } + }); + } + + private void updateShader() { + final float width = mImageView.getWidth(); + final float height = mImageView.getHeight(); + final float distanceNotStretched = mStretchAffectedDistance; + final float normOverScrollDistX = mOverscrollX / width; + final float normOverScrollDistY = mOverscrollY / height; + final float distanceStretchedX = + mStretchAffectedDistance + / (1 + Math.abs(normOverScrollDistX) * mMaxStretchIntensity); + final float distanceStretchedY = + mStretchAffectedDistance + / (1 + Math.abs(normOverScrollDistY) * mMaxStretchIntensity); + final float diffX = distanceStretchedX - distanceNotStretched; + final float diffY = distanceStretchedY - distanceNotStretched; + float uScrollX = mScrollX; + float uScrollY = mScrollY; + + mRuntimeShader.setUniform("uMaxStretchIntensity", mMaxStretchIntensity); + mRuntimeShader.setUniform("uStretchAffectedDist", mStretchAffectedDistance); + mRuntimeShader.setUniform("uDistanceStretchedX", distanceStretchedX); + mRuntimeShader.setUniform("uDistanceStretchedY", distanceStretchedY); + mRuntimeShader.setUniform("uDistDiffX", diffX); + mRuntimeShader.setUniform("uDistDiffY", diffY); + mRuntimeShader.setUniform("uOverscrollX", normOverScrollDistX); + mRuntimeShader.setUniform("uOverscrollY", normOverScrollDistY); + mRuntimeShader.setUniform("uScrollX", uScrollX); + mRuntimeShader.setUniform("uScrollY", uScrollY); + mRuntimeShader.setUniform("viewportWidth", width); + mRuntimeShader.setUniform("viewportHeight", height); + + mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader)); + + mStretchDrawable.setStretchDistance(mStretchAffectedDistance); + mStretchDrawable.setOverscrollX(normOverScrollDistX); + mStretchDrawable.setOverscrollY(normOverScrollDistY); + } + + private static class StretchDrawable extends Drawable { + + private float mStretchDistance = 0; + private float mOverScrollX = 0f; + private float mOverScrollY = 0f; + private Bitmap mBitmap = null; + + public void setStretchDistance(float stretchDistance) { + mStretchDistance = stretchDistance; + invalidateSelf(); + } + + public void setOverscrollX(float overscrollX) { + mOverScrollX = overscrollX; + invalidateSelf(); + } + + public void setOverscrollY(float overscrollY) { + mOverScrollY = overscrollY; + invalidateSelf(); + } + + public void setBitmap(Bitmap bitmap) { + mBitmap = bitmap; + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) { + Rect bounds = getBounds(); + ((RecordingCanvas) canvas).mNode.stretch( + 0, + 0, + bounds.width(), + bounds.height(), + mOverScrollX, + mOverScrollY, + mStretchDistance + ); + } + if (mBitmap != null) { + canvas.drawBitmap(mBitmap, 0f, 0f, null); + } + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return 0; + } + } + + private static final String SKSL = "in shader uContentTexture;\n" + + "uniform float uMaxStretchIntensity; // multiplier to apply to scale effect\n" + + "uniform float uStretchAffectedDist; // Maximum percentage to stretch beyond bounds" + + " of target\n" + + "\n" + + "// Distance stretched as a function of the normalized overscroll times scale " + + "intensity\n" + + "uniform float uDistanceStretchedX;\n" + + "uniform float uDistanceStretchedY;\n" + + "uniform float uDistDiffX;\n" + + "uniform float uDistDiffY; // Difference between the peak stretch amount and " + + "overscroll amount normalized\n" + + "uniform float uScrollX; // Horizontal offset represented as a ratio of pixels " + + "divided by the target width\n" + + "uniform float uScrollY; // Vertical offset represented as a ratio of pixels " + + "divided by the target height\n" + + "uniform float uOverscrollX; // Normalized overscroll amount in the horizontal " + + "direction\n" + + "uniform float uOverscrollY; // Normalized overscroll amount in the vertical " + + "direction\n" + + "\n" + + "uniform float viewportWidth; // target height in pixels\n" + + "uniform float viewportHeight; // target width in pixels\n" + + "\n" + + "vec4 main(vec2 coord) {\n" + + "\n" + + " // Normalize SKSL pixel coordinate into a unit vector\n" + + " vec2 uv = vec2(coord.x / viewportWidth, coord.y / viewportHeight);\n" + + " float inU = uv.x;\n" + + " float inV = uv.y;\n" + + " float outU;\n" + + " float outV;\n" + + " float stretchIntensity;\n" + + "\n" + + " // Add the normalized scroll position within scrolling list\n" + + " inU += uScrollX;\n" + + " inV += uScrollY;\n" + + "\n" + + " outU = inU;\n" + + " outV = inV;\n" + + " if (uOverscrollX > 0) {\n" + + " if (inU <= uStretchAffectedDist) {\n" + + " inU = uStretchAffectedDist - inU;\n" + + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inU);\n" + + " stretchIntensity = uMaxStretchIntensity * uOverscrollX * " + + "posBasedVariation;\n" + + " outU = uDistanceStretchedX - (inU / (1. + stretchIntensity));\n" + + " } else {\n" + + " outU = uDistDiffX + inU;\n" + + " }\n" + + " }\n" + + "\n" + + " if (uOverscrollX < 0) {\n" + + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n" + + " if (inU >= stretchAffectedDist) {\n" + + " inU = inU - stretchAffectedDist;\n" + + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, " + + "inU));\n" + + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollX) * " + + "posBasedVariation;\n" + + " outU = 1 - (uDistanceStretchedX - (inU / (1. + stretchIntensity)))" + + ";\n" + + " } else if (inU < stretchAffectedDist) {\n" + + " outU = -uDistDiffX + inU;\n" + + " }\n" + + " }\n" + + "\n" + + " if (uOverscrollY > 0) {\n" + + " if (inV <= uStretchAffectedDist) {\n" + + " inV = uStretchAffectedDist - inV;\n" + + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inV);\n" + + " stretchIntensity = uMaxStretchIntensity * uOverscrollY * " + + "posBasedVariation;\n" + + " outV = uDistanceStretchedY - (inV / (1. + stretchIntensity));\n" + + " } else if (inV >= uStretchAffectedDist) {\n" + + " outV = uDistDiffY + inV;\n" + + " }\n" + + " }\n" + + "\n" + + " if (uOverscrollY < 0) {\n" + + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n" + + " if (inV >= stretchAffectedDist) {\n" + + " inV = inV - stretchAffectedDist;\n" + + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, inV));\n" + + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollY) * " + + "posBasedVariation;\n" + + " outV = 1 - (uDistanceStretchedY - (inV / (1. + stretchIntensity)));\n" + + " } else if (inV < stretchAffectedDist) {\n" + + " outV = -uDistDiffY + inV;\n" + + " }\n" + + " }\n" + + "\n" + + " uv.x = outU;\n" + + " uv.y = outV;\n" + + " coord.x = uv.x * viewportWidth;\n" + + " coord.y = uv.y * viewportHeight;\n" + + " return sample(uContentTexture, coord);\n" + + "}"; +} diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt new file mode 100644 index 000000000000..2e985fbba269 --- /dev/null +++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.input + +import android.view.InputDevice.SOURCE_MOUSE +import android.view.InputDevice.SOURCE_TOUCHSCREEN +import android.view.InputEventAssigner +import android.view.KeyEvent +import android.view.MotionEvent +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Create a MotionEvent with the provided action, eventTime, and source + */ +fun createMotionEvent(action: Int, eventTime: Long, source: Int): MotionEvent { + val downTime: Long = 10 + val x = 1f + val y = 2f + val pressure = 3f + val size = 1f + val metaState = 0 + val xPrecision = 0f + val yPrecision = 0f + val deviceId = 1 + val edgeFlags = 0 + val displayId = 0 + return MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, size, metaState, + xPrecision, yPrecision, deviceId, edgeFlags, source, displayId) +} + +fun createKeyEvent(action: Int, eventTime: Long): KeyEvent { + val code = KeyEvent.KEYCODE_A + val repeat = 0 + return KeyEvent(eventTime, eventTime, action, code, repeat) +} + +class InputEventAssignerTest { + companion object { + private const val TAG = "InputEventAssignerTest" + } + + /** + * A single MOVE event should be assigned to the next available frame. + */ + @Test + fun testTouchGesture() { + val assigner = InputEventAssigner() + val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN) + val eventId = assigner.processEvent(event) + assertEquals(event.id, eventId) + } + + /** + * DOWN event should be used until a vsync comes in. After vsync, the latest event should be + * produced. + */ + @Test + fun testTouchDownWithMove() { + val assigner = InputEventAssigner() + val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN) + val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN) + val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN) + val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN) + val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN) + var eventId = assigner.processEvent(down) + assertEquals(down.id, eventId) + eventId = assigner.processEvent(move1) + assertEquals(down.id, eventId) + eventId = assigner.processEvent(move2) + // Even though we already had 2 move events, there was no choreographer callback yet. + // Therefore, we should still get the id of the down event + assertEquals(down.id, eventId) + + // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event + assigner.onChoreographerCallback() + eventId = assigner.processEvent(move3) + assertEquals(move3.id, eventId) + eventId = assigner.processEvent(move4) + assertEquals(move4.id, eventId) + } + + /** + * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency + * concept for non-touchscreens, the latest input event will be used. + */ + @Test + fun testMouseDownWithMove() { + val assigner = InputEventAssigner() + val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE) + val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE) + var eventId = assigner.processEvent(down) + assertEquals(down.id, eventId) + eventId = assigner.processEvent(move1) + assertEquals(move1.id, eventId) + } + + /** + * KeyEvents are processed immediately, so the latest event should be returned. + */ + @Test + fun testKeyEvent() { + val assigner = InputEventAssigner() + val down = createKeyEvent(KeyEvent.ACTION_DOWN, 20) + var eventId = assigner.processEvent(down) + assertEquals(down.id, eventId) + val up = createKeyEvent(KeyEvent.ACTION_UP, 21) + eventId = assigner.processEvent(up) + // DOWN is only sticky for Motions, not for keys + assertEquals(up.id, eventId) + assigner.onChoreographerCallback() + val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22) + eventId = assigner.processEvent(down2) + assertEquals(down2.id, eventId) + } +} diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt index c19e5cc34611..c01d32bf4cd2 100644 --- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt +++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt @@ -17,6 +17,7 @@ package com.android.test.input import android.graphics.FrameInfo +import android.os.IInputConstants.INVALID_INPUT_EVENT_ID import android.os.SystemClock import android.view.ViewFrameInfo import com.google.common.truth.Truth.assertThat @@ -33,8 +34,7 @@ class ViewFrameInfoTest { @Before fun setUp() { mViewFrameInfo.reset() - mViewFrameInfo.updateOldestInputEvent(10) - mViewFrameInfo.updateNewestInputEvent(20) + mViewFrameInfo.setInputEvent(139) mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED mTimeStarted = SystemClock.uptimeNanos() mViewFrameInfo.markDrawStart() @@ -43,8 +43,6 @@ class ViewFrameInfoTest { @Test fun testPopulateFields() { assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted) - assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(10) - assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(20) assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED) } @@ -53,8 +51,6 @@ class ViewFrameInfoTest { mViewFrameInfo.reset() // Ensure that the original object is reset correctly assertThat(mViewFrameInfo.drawStart).isEqualTo(0) - assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(0) - assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(0) assertThat(mViewFrameInfo.flags).isEqualTo(0) } @@ -62,12 +58,13 @@ class ViewFrameInfoTest { fun testUpdateFrameInfoFromViewFrameInfo() { val frameInfo = FrameInfo() // By default, all values should be zero - // TODO(b/169866723): Use InputEventAssigner and assert INPUT_EVENT_ID + assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(INVALID_INPUT_EVENT_ID) assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0) assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0) // The values inside FrameInfo should match those from ViewFrameInfo after we update them mViewFrameInfo.populateFrameInfo(frameInfo) + assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(139) assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo( FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED) assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted) diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp index ee24d48f0ed5..d4f1ad317d31 100644 --- a/tests/UpdatableSystemFontTest/Android.bp +++ b/tests/UpdatableSystemFontTest/Android.bp @@ -26,13 +26,9 @@ java_test_host { srcs: ["src/**/*.java"], libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"], static_libs: [ - "block_device_writer_jar", "frameworks-base-hostutils", ], test_suites: ["general-tests", "vts"], - target_required: [ - "block_device_writer_module", - ], data: [ ":NotoColorEmojiTtf", ":UpdatableSystemFontTestCertDer", diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml index 7b919bd4b114..efe5d703880c 100644 --- a/tests/UpdatableSystemFontTest/AndroidTest.xml +++ b/tests/UpdatableSystemFontTest/AndroidTest.xml @@ -21,7 +21,6 @@ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> - <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" /> <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" /> <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" /> diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index e249f8a99b0c..92fa498f8326 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.platform.test.annotations.RootPermissionTest; -import com.android.blockdevicewriter.BlockDeviceWriter; import com.android.fsverity.AddFsVerityCertRule; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.log.LogUtil.CLog; @@ -144,30 +143,6 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test { assertThat(fontPathAfterReboot).isEqualTo(fontPath); } - @Test - public void reboot_clearDamagedFiles() throws Exception { - expectRemoteCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG)); - String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); - assertThat(fontPath).startsWith("/data/fonts/files/"); - assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isTrue(); - - BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), fontPath, 0); - expectRemoteCommandToSucceed("stop"); - // We have to make sure system_server is gone before dropping caches, because system_server - // process holds font memory maps and prevents cache eviction. - waitUntilSystemServerIsGone(); - BlockDeviceWriter.assertFileNotOpen(getDevice(), fontPath); - BlockDeviceWriter.dropCaches(getDevice()); - assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isFalse(); - - expectRemoteCommandToSucceed("start"); - waitUntilFontCommandIsReady(); - String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF); - assertWithMessage("Damaged file should be deleted") - .that(fontPathAfterReboot).startsWith("/system"); - } - private String getFontPath(String fontFileName) throws Exception { // TODO: add a dedicated command for testing. String lines = expectRemoteCommandToSucceed("cmd font dump"); diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java index 7a60cc105a26..15d3398d43c0 100644 --- a/tests/net/common/java/android/net/CaptivePortalTest.java +++ b/tests/net/common/java/android/net/CaptivePortalTest.java @@ -24,8 +24,8 @@ import android.os.RemoteException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import org.junit.Rule; @@ -55,7 +55,7 @@ public class CaptivePortalTest { mCode = request; } - @Override + // This is only @Override on R- public void logEvent(int eventId, String packageName) throws RemoteException { mCode = eventId; mPackageName = packageName; @@ -98,12 +98,24 @@ public class CaptivePortalTest { assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED); } + @IgnoreUpTo(Build.VERSION_CODES.R) @Test public void testLogEvent() { + /** + * From S testLogEvent is expected to do nothing but shouldn't crash (the API + * logEvent has been deprecated). + */ final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( - MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY, + 0, TEST_PACKAGE_NAME)); - assertEquals(result.mCode, MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY); + } + + @IgnoreAfter(Build.VERSION_CODES.R) + @Test + public void testLogEvent_UntilR() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( + 42, TEST_PACKAGE_NAME)); + assertEquals(result.mCode, 42); assertEquals(result.mPackageName, TEST_PACKAGE_NAME); } } diff --git a/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt b/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt index 56b56efd501b..0ca4d9551f39 100644 --- a/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt +++ b/tests/net/common/java/android/net/NetworkStateSnapshotTest.kt @@ -63,10 +63,10 @@ class NetworkStateSnapshotTest { @Test fun testParcelUnparcel() { - val emptySnapshot = NetworkStateSnapshot(LinkProperties(), NetworkCapabilities(), - Network(TEST_NETID), null, TYPE_NONE) + val emptySnapshot = NetworkStateSnapshot(Network(TEST_NETID), NetworkCapabilities(), + LinkProperties(), null, TYPE_NONE) val snapshot = NetworkStateSnapshot( - TEST_LINK_PROPERTIES, TEST_CAPABILITIES, Network(TEST_NETID), TEST_IMSI, TYPE_WIFI) + Network(TEST_NETID), TEST_CAPABILITIES, TEST_LINK_PROPERTIES, TEST_IMSI, TYPE_WIFI) assertParcelSane(emptySnapshot, 5) assertParcelSane(snapshot, 5) } diff --git a/tests/net/common/java/android/net/UidRangeTest.java b/tests/net/common/java/android/net/UidRangeTest.java new file mode 100644 index 000000000000..1b1c95431d6f --- /dev/null +++ b/tests/net/common/java/android/net/UidRangeTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.os.UserHandle.MIN_SECONDARY_USER_ID; +import static android.os.UserHandle.SYSTEM; +import static android.os.UserHandle.USER_SYSTEM; +import static android.os.UserHandle.getUid; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Build; +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class UidRangeTest { + + /* + * UidRange is no longer passed to netd. UID ranges between the framework and netd are passed as + * UidRangeParcel objects. + */ + + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); + + @Test + public void testSingleItemUidRangeAllowed() { + new UidRange(123, 123); + new UidRange(0, 0); + new UidRange(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + @Test + public void testNegativeUidsDisallowed() { + try { + new UidRange(-2, 100); + fail("Exception not thrown for negative start UID"); + } catch (IllegalArgumentException expected) { + } + + try { + new UidRange(-200, -100); + fail("Exception not thrown for negative stop UID"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testStopLessThanStartDisallowed() { + final int x = 4195000; + try { + new UidRange(x, x - 1); + fail("Exception not thrown for negative-length UID range"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testGetStartAndEndUser() throws Exception { + final UidRange uidRangeOfPrimaryUser = new UidRange( + getUid(USER_SYSTEM, 10000), getUid(USER_SYSTEM, 10100)); + final UidRange uidRangeOfSecondaryUser = new UidRange( + getUid(MIN_SECONDARY_USER_ID, 10000), getUid(MIN_SECONDARY_USER_ID, 10100)); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getStartUser()); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getEndUser()); + assertEquals(MIN_SECONDARY_USER_ID, uidRangeOfSecondaryUser.getStartUser()); + assertEquals(MIN_SECONDARY_USER_ID, uidRangeOfSecondaryUser.getEndUser()); + + final UidRange uidRangeForDifferentUsers = new UidRange( + getUid(USER_SYSTEM, 10000), getUid(MIN_SECONDARY_USER_ID, 10100)); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getStartUser()); + assertEquals(MIN_SECONDARY_USER_ID, uidRangeOfSecondaryUser.getEndUser()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testCreateForUser() throws Exception { + final UidRange uidRangeOfPrimaryUser = UidRange.createForUser(SYSTEM); + final UidRange uidRangeOfSecondaryUser = UidRange.createForUser( + UserHandle.of(USER_SYSTEM + 1)); + assertTrue(uidRangeOfPrimaryUser.stop < uidRangeOfSecondaryUser.start); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getStartUser()); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getEndUser()); + assertEquals(USER_SYSTEM + 1, uidRangeOfSecondaryUser.getStartUser()); + assertEquals(USER_SYSTEM + 1, uidRangeOfSecondaryUser.getEndUser()); + } +} diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index 9ed55f098a16..2a2dc5628ecd 100644 --- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -16,6 +16,7 @@ package com.android.server.net.integrationtests +import android.app.usage.NetworkStatsManager import android.content.ComponentName import android.content.Context import android.content.Context.BIND_AUTO_CREATE @@ -25,7 +26,6 @@ import android.content.ServiceConnection import android.net.ConnectivityManager import android.net.IDnsResolver import android.net.INetd -import android.net.INetworkStatsService import android.net.LinkProperties import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET @@ -37,7 +37,6 @@ import android.net.Uri import android.net.metrics.IpConnectivityLog import android.os.ConditionVariable import android.os.IBinder -import android.os.INetworkManagementService import android.os.SystemConfigManager import android.os.UserHandle import android.testing.TestableContext @@ -87,9 +86,7 @@ class ConnectivityServiceIntegrationTest { // lateinit used here for mocks as they need to be reinitialized between each test and the test // should crash if they are used before being initialized. @Mock - private lateinit var netManager: INetworkManagementService - @Mock - private lateinit var statsService: INetworkStatsService + private lateinit var statsManager: NetworkStatsManager @Mock private lateinit var log: IpConnectivityLog @Mock @@ -172,12 +169,13 @@ class ConnectivityServiceIntegrationTest { service = TestConnectivityService(makeDependencies()) cm = ConnectivityManager(context, service) context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm) + context.addMockSystemService(Context.NETWORK_STATS_SERVICE, statsManager) service.systemReadyInternal() } private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService( - context, netManager, statsService, dnsResolver, log, netd, deps) + context, dnsResolver, log, netd, deps) private fun makeDependencies(): ConnectivityService.Dependencies { val deps = spy(ConnectivityService.Dependencies()) diff --git a/tests/net/java/android/net/IpSecAlgorithmTest.java b/tests/net/java/android/net/IpSecAlgorithmTest.java index 2e1c29a2e405..3a8d6004f66f 100644 --- a/tests/net/java/android/net/IpSecAlgorithmTest.java +++ b/tests/net/java/android/net/IpSecAlgorithmTest.java @@ -129,6 +129,7 @@ public class IpSecAlgorithmTest { checkCryptKeyLenValidation(IpSecAlgorithm.CRYPT_AES_CTR, len); } checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_XCBC, 128, 96); + checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_CMAC, 128, 96); checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305, 288, 128); } diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt index 27224c216db3..64b774cc4340 100644 --- a/tests/net/java/android/net/NetworkTemplateTest.kt +++ b/tests/net/java/android/net/NetworkTemplateTest.kt @@ -20,14 +20,13 @@ import android.content.Context import android.net.ConnectivityManager.TYPE_MOBILE import android.net.ConnectivityManager.TYPE_WIFI import android.net.NetworkIdentity.SUBTYPE_COMBINED -import android.net.NetworkIdentity.OEM_NONE; -import android.net.NetworkIdentity.OEM_PAID; -import android.net.NetworkIdentity.OEM_PRIVATE; +import android.net.NetworkIdentity.OEM_NONE +import android.net.NetworkIdentity.OEM_PAID +import android.net.NetworkIdentity.OEM_PRIVATE import android.net.NetworkIdentity.buildNetworkIdentity import android.net.NetworkStats.DEFAULT_NETWORK_ALL import android.net.NetworkStats.METERED_ALL import android.net.NetworkStats.ROAMING_ALL -import android.net.NetworkTemplate.MATCH_ETHERNET import android.net.NetworkTemplate.MATCH_MOBILE import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD import android.net.NetworkTemplate.MATCH_WIFI @@ -50,7 +49,6 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue -import kotlin.test.fail private const val TEST_IMSI1 = "imsi1" private const val TEST_IMSI2 = "imsi2" @@ -60,17 +58,17 @@ private const val TEST_SSID1 = "ssid1" class NetworkTemplateTest { private val mockContext = mock(Context::class.java) - private fun buildMobileNetworkState(subscriberId: String): NetworkState = + private fun buildMobileNetworkState(subscriberId: String): NetworkStateSnapshot = buildNetworkState(TYPE_MOBILE, subscriberId = subscriberId) - private fun buildWifiNetworkState(ssid: String): NetworkState = + private fun buildWifiNetworkState(ssid: String): NetworkStateSnapshot = buildNetworkState(TYPE_WIFI, ssid = ssid) private fun buildNetworkState( type: Int, subscriberId: String? = null, ssid: String? = null, - oemManaged: Int = OEM_NONE, - ): NetworkState { + oemManaged: Int = OEM_NONE + ): NetworkStateSnapshot { val lp = LinkProperties() val caps = NetworkCapabilities().apply { setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false) @@ -81,7 +79,7 @@ class NetworkTemplateTest { setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, (oemManaged and OEM_PRIVATE) == OEM_PRIVATE) } - return NetworkState(type, lp, caps, mock(Network::class.java), subscriberId) + return NetworkStateSnapshot(mock(Network::class.java), caps, lp, subscriberId, type) } private fun NetworkTemplate.assertMatches(ident: NetworkIdentity) = @@ -179,7 +177,7 @@ class NetworkTemplateTest { OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE) // Verify that "not OEM managed network" constants are equal. - assertEquals(OEM_MANAGED_NO, OEM_NONE); + assertEquals(OEM_MANAGED_NO, OEM_NONE) // Verify the constants don't conflict. assertEquals(constantValues.size, constantValues.distinct().count()) @@ -201,8 +199,13 @@ class NetworkTemplateTest { * @param identSsid If networkType is {@code TYPE_WIFI}, this value must *NOT* be null. Provide * one of {@code TEST_SSID*}. */ - private fun matchOemManagedIdent(networkType: Int, matchType:Int, subscriberId: String? = null, - templateSsid: String? = null, identSsid: String? = null) { + private fun matchOemManagedIdent( + networkType: Int, + matchType: Int, + subscriberId: String? = null, + templateSsid: String? = null, + identSsid: String? = null + ) { val oemManagedStates = arrayOf(OEM_NONE, OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE) // A null subscriberId needs a null matchSubscriberIds argument as well. val matchSubscriberIds = if (subscriberId == null) null else arrayOf(subscriberId) diff --git a/tests/net/java/android/net/UidRangeTest.java b/tests/net/java/android/net/UidRangeTest.java deleted file mode 100644 index ea1df096e208..000000000000 --- a/tests/net/java/android/net/UidRangeTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import static org.junit.Assert.fail; - -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 UidRangeTest { - - /* - * UidRange is no longer passed to netd. UID ranges between the framework and netd are passed as - * UidRangeParcel objects. - */ - - @Test - public void testSingleItemUidRangeAllowed() { - new UidRange(123, 123); - new UidRange(0, 0); - new UidRange(Integer.MAX_VALUE, Integer.MAX_VALUE); - } - - @Test - public void testNegativeUidsDisallowed() { - try { - new UidRange(-2, 100); - fail("Exception not thrown for negative start UID"); - } catch (IllegalArgumentException expected) { - } - - try { - new UidRange(-200, -100); - fail("Exception not thrown for negative stop UID"); - } catch (IllegalArgumentException expected) { - } - } - - @Test - public void testStopLessThanStartDisallowed() { - final int x = 4195000; - try { - new UidRange(x, x - 1); - fail("Exception not thrown for negative-length UID range"); - } catch (IllegalArgumentException expected) { - } - } -}
\ No newline at end of file diff --git a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt index 9b0cfa9db30f..c1315f64c56b 100644 --- a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt +++ b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt @@ -21,7 +21,7 @@ import android.content.res.Resources import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY -import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdChangedListener +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 @@ -120,9 +120,9 @@ class MultinetworkPolicyTrackerTest { MULTIPATH_PREFERENCE_PERFORMANCE.toString()) val listenerCaptor = ArgumentCaptor.forClass( - ActiveDataSubscriptionIdChangedListener::class.java) + ActiveDataSubscriptionIdListener::class.java) verify(telephonyManager, times(1)) - .registerPhoneStateListener(any(), listenerCaptor.capture()) + .registerTelephonyCallback(any(), listenerCaptor.capture()) val listener = listenerCaptor.value listener.onActiveDataSubscriptionIdChanged(testSubId) diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 1cfc3f9f9e5c..d72517147e4d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -91,6 +91,10 @@ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; @@ -99,6 +103,7 @@ import static com.android.testutils.ConcurrentUtils.await; import static com.android.testutils.ConcurrentUtils.durationOf; import static com.android.testutils.ExceptionUtils.ignoreExceptions; import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor; +import static com.android.testutils.MiscAsserts.assertContainsAll; import static com.android.testutils.MiscAsserts.assertContainsExactly; import static com.android.testutils.MiscAsserts.assertEmpty; import static com.android.testutils.MiscAsserts.assertLength; @@ -145,6 +150,7 @@ import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.usage.NetworkStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -176,7 +182,6 @@ import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.INetworkStatsService; import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.InetAddresses; @@ -199,7 +204,7 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkTestResultParcelable; import android.net.OemNetworkPreferences; import android.net.ProxyInfo; @@ -218,6 +223,8 @@ import android.net.Uri; import android.net.VpnManager; import android.net.VpnTransportInfo; import android.net.metrics.IpConnectivityLog; +import android.net.resolv.aidl.Nat64PrefixEventParcel; +import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; @@ -244,7 +251,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; -import android.security.KeyStore; import android.system.Os; import android.telephony.TelephonyManager; import android.telephony.data.EpsBearerQosSessionAttributes; @@ -276,6 +282,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; import com.android.server.connectivity.Vpn; +import com.android.server.connectivity.VpnProfileStore; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.testutils.ExceptionUtils; @@ -345,6 +352,9 @@ public class ConnectivityServiceTest { private static final String TAG = "ConnectivityServiceTest"; private static final int TIMEOUT_MS = 500; + // Broadcasts can take a long time to be delivered. The test will not wait for that long unless + // there is a failure, so use a long timeout. + private static final int BROADCAST_TIMEOUT_MS = 30_000; private static final int TEST_LINGER_DELAY_MS = 400; private static final int TEST_NASCENT_DELAY_MS = 300; // Chosen to be less than the linger and nascent timeout. This ensures that we can distinguish @@ -407,6 +417,8 @@ public class ConnectivityServiceTest { private QosCallbackMockHelper mQosCallbackMockHelper; private QosCallbackTracker mQosCallbackTracker; private VpnManagerService mVpnManagerService; + private TestNetworkCallback mDefaultNetworkCallback; + private TestNetworkCallback mSystemDefaultNetworkCallback; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -414,7 +426,7 @@ public class ConnectivityServiceTest { @Mock DeviceIdleInternal mDeviceIdleInternal; @Mock INetworkManagementService mNetworkManagementService; - @Mock INetworkStatsService mStatsService; + @Mock NetworkStatsManager mStatsManager; @Mock IBatteryStats mBatteryStatsService; @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; @@ -431,7 +443,7 @@ public class ConnectivityServiceTest { @Mock MockableSystemProperties mSystemProperties; @Mock EthernetManager mEthernetManager; @Mock NetworkPolicyManager mNetworkPolicyManager; - @Mock KeyStore mKeyStore; + @Mock VpnProfileStore mVpnProfileStore; @Mock SystemConfigManager mSystemConfigManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = @@ -530,6 +542,7 @@ public class ConnectivityServiceTest { if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager; if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager; if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager; + if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager; return super.getSystemService(name); } @@ -1072,9 +1085,11 @@ public class ConnectivityServiceTest { } } - private Set<UidRange> uidRangesForUid(int uid) { + private Set<UidRange> uidRangesForUids(int... uids) { final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); + for (final int uid : uids) { + ranges.add(new UidRange(uid, uid)); + } return ranges; } @@ -1115,7 +1130,7 @@ public class ConnectivityServiceTest { return mDeviceIdleInternal; } }, - mNetworkManagementService, mMockNetd, userId, mKeyStore); + mNetworkManagementService, mMockNetd, userId, mVpnProfileStore); } public void setUids(Set<UidRange> uids) { @@ -1204,13 +1219,13 @@ public class ConnectivityServiceTest { public void establishForMyUid(LinkProperties lp) throws Exception { final int uid = Process.myUid(); - establish(lp, uid, uidRangesForUid(uid), true, true, false); + establish(lp, uid, uidRangesForUids(uid), true, true, false); } public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode) throws Exception { final int uid = Process.myUid(); - establish(makeLinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet, + establish(makeLinkProperties(), uid, uidRangesForUids(uid), validated, hasInternet, isStrictMode); } @@ -1294,8 +1309,9 @@ public class ConnectivityServiceTest { return mVMSHandlerThread; } - public KeyStore getKeyStore() { - return mKeyStore; + @Override + public VpnProfileStore getVpnProfileStore() { + return mVpnProfileStore; } public INetd getNetd() { @@ -1318,7 +1334,7 @@ public class ConnectivityServiceTest { } - private void processBroadcastForVpn(Intent intent) { + private void processBroadcast(Intent intent) { mServiceContext.sendBroadcast(intent); HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS); waitForIdle(); @@ -1409,6 +1425,7 @@ public class ConnectivityServiceTest { private static final int VPN_UID = UserHandle.getUid(PRIMARY_USER, 10043); private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "", UserInfo.FLAG_PRIMARY); + private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER); private static final int RESTRICTED_USER = 1; private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "", @@ -1426,6 +1443,8 @@ public class ConnectivityServiceTest { MockitoAnnotations.initMocks(this); when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO)); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE)); when(mUserManager.getUserInfo(PRIMARY_USER)).thenReturn(PRIMARY_USER_INFO); // canHaveRestrictedProfile does not take a userId. It applies to the userId of the context // it was started from, i.e., PRIMARY_USER. @@ -1462,8 +1481,6 @@ public class ConnectivityServiceTest { mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, - mNetworkManagementService, - mStatsService, mMockDnsResolver, mock(IpConnectivityLog.class), mMockNetd, @@ -1547,6 +1564,7 @@ public class ConnectivityServiceTest { @After public void tearDown() throws Exception { + unregisterDefaultNetworkCallbacks(); setAlwaysOnNetworks(false); if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); @@ -1652,6 +1670,7 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetworkForUid(Process.myUid())); // Test getAllNetworks() assertEmpty(mCm.getAllNetworks()); + assertEmpty(mCm.getAllNetworkStateSnapshot()); } /** @@ -1680,7 +1699,7 @@ public class ConnectivityServiceTest { } public Intent expectBroadcast() throws Exception { - return expectBroadcast(TIMEOUT_MS); + return expectBroadcast(BROADCAST_TIMEOUT_MS); } public void expectNoBroadcast(int timeoutMs) throws Exception { @@ -5478,18 +5497,19 @@ public class ConnectivityServiceTest { assertEquals(expectedSet, actualSet); } - private void expectForceUpdateIfaces(Network[] networks, String defaultIface, + private void expectNetworkStatus(Network[] networks, String defaultIface, Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception { - ArgumentCaptor<Network[]> networksCaptor = ArgumentCaptor.forClass(Network[].class); - ArgumentCaptor<UnderlyingNetworkInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass( - UnderlyingNetworkInfo[].class); + ArgumentCaptor<List<Network>> networksCaptor = ArgumentCaptor.forClass(List.class); + ArgumentCaptor<List<UnderlyingNetworkInfo>> vpnInfosCaptor = + ArgumentCaptor.forClass(List.class); - verify(mStatsService, atLeastOnce()).forceUpdateIfaces(networksCaptor.capture(), - any(NetworkState[].class), eq(defaultIface), vpnInfosCaptor.capture()); + verify(mStatsManager, atLeastOnce()).notifyNetworkStatus(networksCaptor.capture(), + any(List.class), eq(defaultIface), vpnInfosCaptor.capture()); - assertSameElementsNoDuplicates(networksCaptor.getValue(), networks); + assertSameElementsNoDuplicates(networksCaptor.getValue().toArray(), networks); - UnderlyingNetworkInfo[] infos = vpnInfosCaptor.getValue(); + UnderlyingNetworkInfo[] infos = + vpnInfosCaptor.getValue().toArray(new UnderlyingNetworkInfo[0]); if (vpnUid != null) { assertEquals("Should have exactly one VPN:", 1, infos.length); UnderlyingNetworkInfo info = infos[0]; @@ -5503,8 +5523,9 @@ public class ConnectivityServiceTest { } } - private void expectForceUpdateIfaces(Network[] networks, String defaultIface) throws Exception { - expectForceUpdateIfaces(networks, defaultIface, null, null, new String[0]); + private void expectNetworkStatus( + Network[] networks, String defaultIface) throws Exception { + expectNetworkStatus(networks, defaultIface, null, null, new String[0]); } @Test @@ -5524,47 +5545,46 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Default network switch should update ifaces. mWiFiNetworkAgent.connect(false); mWiFiNetworkAgent.sendLinkProperties(wifiLp); waitForIdle(); assertEquals(wifiLp, mService.getActiveLinkProperties()); - expectForceUpdateIfaces(onlyWifi, WIFI_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyWifi, WIFI_IFNAME); + reset(mStatsManager); // Disconnect should update ifaces. mWiFiNetworkAgent.disconnect(); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Metered change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Temp metered change shouldn't update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED); waitForIdle(); - verify(mStatsService, never()) - .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME), - eq(new UnderlyingNetworkInfo[0])); - reset(mStatsService); + verify(mStatsManager, never()).notifyNetworkStatus(eq(Arrays.asList(onlyCell)), + any(List.class), eq(MOBILE_IFNAME), any(List.class)); + reset(mStatsManager); // Roaming change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); waitForIdle(); - expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); - reset(mStatsService); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); // Test VPNs. final LinkProperties lp = new LinkProperties(); @@ -5577,7 +5597,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; // A VPN with default (null) underlying networks sets the underlying network's interfaces... - expectForceUpdateIfaces(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME}); // ...and updates them as the default network switches. @@ -5594,9 +5614,9 @@ public class ConnectivityServiceTest { waitForIdle(); assertEquals(wifiLp, mService.getActiveLinkProperties()); - expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); // A VPN that sets its underlying networks passes the underlying interfaces, and influences // the default interface sent to NetworkStatsService by virtue of applying to the system @@ -5606,22 +5626,22 @@ public class ConnectivityServiceTest { // applies to the system server UID should not have any bearing on network stats. mMockVpn.setUnderlyingNetworks(onlyCell); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME}); - reset(mStatsService); + reset(mStatsManager); mMockVpn.setUnderlyingNetworks(cellAndWifi); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME, WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); // Null underlying networks are ignored. mMockVpn.setUnderlyingNetworks(cellNullAndWifi); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME, WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); // If an underlying network disconnects, that interface should no longer be underlying. // This doesn't actually work because disconnectAndDestroyNetwork only notifies @@ -5633,17 +5653,17 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); waitForIdle(); assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork())); - expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{MOBILE_IFNAME, WIFI_IFNAME}); // Confirm that we never tell NetworkStatsService that cell is no longer the underlying // network for the VPN... - verify(mStatsService, never()).forceUpdateIfaces(any(Network[].class), - any(NetworkState[].class), any() /* anyString() doesn't match null */, - argThat(infos -> infos[0].underlyingIfaces.size() == 1 - && WIFI_IFNAME.equals(infos[0].underlyingIfaces.get(0)))); - verifyNoMoreInteractions(mStatsService); - reset(mStatsService); + verify(mStatsManager, never()).notifyNetworkStatus(any(List.class), + any(List.class), any() /* anyString() doesn't match null */, + argThat(infos -> infos.get(0).underlyingIfaces.size() == 1 + && WIFI_IFNAME.equals(infos.get(0).underlyingIfaces.get(0)))); + verifyNoMoreInteractions(mStatsManager); + reset(mStatsManager); // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be // called again, it does. For example, connect Ethernet, but with a low score, such that it @@ -5652,13 +5672,13 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.adjustScore(-40); mEthernetNetworkAgent.connect(false); waitForIdle(); - verify(mStatsService).forceUpdateIfaces(any(Network[].class), - any(NetworkState[].class), any() /* anyString() doesn't match null */, - argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.size() == 1 - && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces.get(0)))); + verify(mStatsManager).notifyNetworkStatus(any(List.class), + any(List.class), any() /* anyString() doesn't match null */, + argThat(vpnInfos -> vpnInfos.get(0).underlyingIfaces.size() == 1 + && WIFI_IFNAME.equals(vpnInfos.get(0).underlyingIfaces.get(0)))); mEthernetNetworkAgent.disconnect(); waitForIdle(); - reset(mStatsService); + reset(mStatsManager); // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes @@ -5668,27 +5688,27 @@ public class ConnectivityServiceTest { // Also, for the same reason as above, the active interface passed in is null. mMockVpn.setUnderlyingNetworks(new Network[0]); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, null); - reset(mStatsService); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); // Specifying only a null underlying network is the same as no networks. mMockVpn.setUnderlyingNetworks(onlyNull); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, null); - reset(mStatsService); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); // Specifying networks that are all disconnected is the same as specifying no networks. mMockVpn.setUnderlyingNetworks(onlyCell); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, null); - reset(mStatsService); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); // Passing in null again means follow the default network again. mMockVpn.setUnderlyingNetworks(null); waitForIdle(); - expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + expectNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, new String[]{WIFI_IFNAME}); - reset(mStatsService); + reset(mStatsManager); } @Test @@ -5921,6 +5941,16 @@ public class ConnectivityServiceTest { assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName()); } + private PrivateDnsValidationEventParcel makePrivateDnsValidationEvent( + final int netId, final String ipAddress, final String hostname, final int validation) { + final PrivateDnsValidationEventParcel event = new PrivateDnsValidationEventParcel(); + event.netId = netId; + event.ipAddress = ipAddress; + event.hostname = hostname; + event.validation = validation; + return event; + } + @Test public void testLinkPropertiesWithPrivateDnsValidationEvents() throws Exception { // The default on Android is opportunistic mode ("Automatic"). @@ -5951,8 +5981,9 @@ public class ConnectivityServiceTest { // Send a validation event for a server that is not part of the current // resolver config. The validation event should be ignored. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "", "145.100.185.18", true); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, "", + "145.100.185.18", VALIDATION_RESULT_SUCCESS)); cellNetworkCallback.assertNoCallback(); // Add a dns server to the LinkProperties. @@ -5969,20 +6000,23 @@ public class ConnectivityServiceTest { // Send a validation event containing a hostname that is not part of // the current resolver config. The validation event should be ignored. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "hostname", true); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "hostname", VALIDATION_RESULT_SUCCESS)); cellNetworkCallback.assertNoCallback(); // Send a validation event where validation failed. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", false); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "", VALIDATION_RESULT_FAILURE)); cellNetworkCallback.assertNoCallback(); // Send a validation event where validation succeeded for a server in // the current resolver config. A LinkProperties callback with updated // private dns fields should be sent. - mService.mNetdEventCallback.onPrivateDnsValidationEvent( - mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true); + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "", VALIDATION_RESULT_SUCCESS)); cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); @@ -6361,7 +6395,7 @@ public class ConnectivityServiceTest { vpnNetworkCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - final Set<UidRange> ranges = uidRangesForUid(uid); + final Set<UidRange> ranges = uidRangesForUids(uid); mMockVpn.registerAgent(ranges); mMockVpn.setUnderlyingNetworks(new Network[0]); @@ -6833,7 +6867,7 @@ public class ConnectivityServiceTest { final int uid = Process.myUid(); NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertNotNull("nc=" + nc, nc.getUids()); - assertEquals(nc.getUids(), uidRangesForUid(uid)); + assertEquals(nc.getUids(), uidRangesForUids(uid)); assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); // Set an underlying network and expect to see the VPN transports change. @@ -6854,7 +6888,7 @@ public class ConnectivityServiceTest { addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); // Send a USER_ADDED broadcast for it. - processBroadcastForVpn(addedIntent); + processBroadcast(addedIntent); // Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added // restricted user. @@ -6879,7 +6913,7 @@ public class ConnectivityServiceTest { final Intent removedIntent = new Intent(ACTION_USER_REMOVED); removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); - processBroadcastForVpn(removedIntent); + processBroadcast(removedIntent); // Expect that the VPN gains the UID range for the restricted user, and that the capability // change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved. @@ -6937,7 +6971,7 @@ public class ConnectivityServiceTest { final Intent addedIntent = new Intent(ACTION_USER_ADDED); addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); - processBroadcastForVpn(addedIntent); + processBroadcast(addedIntent); assertNull(mCm.getActiveNetworkForUid(uid)); assertNull(mCm.getActiveNetworkForUid(restrictedUid)); @@ -6948,7 +6982,7 @@ public class ConnectivityServiceTest { final Intent removedIntent = new Intent(ACTION_USER_REMOVED); removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); - processBroadcastForVpn(removedIntent); + processBroadcast(removedIntent); assertNull(mCm.getActiveNetworkForUid(uid)); assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); @@ -7103,7 +7137,7 @@ public class ConnectivityServiceTest { assertFalse(mCm.isActiveNetworkMetered()); // Connect VPN network. - mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUid(Process.myUid()), + mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUids(Process.myUid()), new LinkProperties()); mMockVpn.connect(true); waitForIdle(); @@ -7488,8 +7522,7 @@ public class ConnectivityServiceTest { private void setupLegacyLockdownVpn() { final String profileName = "testVpnProfile"; final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8); - when(mKeyStore.contains(Credentials.LOCKDOWN_VPN)).thenReturn(true); - when(mKeyStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag); + when(mVpnProfileStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag); final VpnProfile profile = new VpnProfile(profileName); profile.name = "My VPN"; @@ -7497,7 +7530,7 @@ public class ConnectivityServiceTest { profile.dnsServers = "8.8.8.8"; profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK; final byte[] encodedProfile = profile.encode(); - when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); + when(mVpnProfileStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); } private void establishLegacyLockdownVpn(Network underlying) throws Exception { @@ -7543,7 +7576,7 @@ public class ConnectivityServiceTest { final Intent addedIntent = new Intent(ACTION_USER_UNLOCKED); addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - processBroadcastForVpn(addedIntent); + processBroadcast(addedIntent); // Lockdown VPN disables teardown and enables lockdown. assertFalse(mMockVpn.getEnableTeardown()); @@ -7826,6 +7859,16 @@ public class ConnectivityServiceTest { return stacked; } + private Nat64PrefixEventParcel makeNat64PrefixEvent(final int netId, final int prefixOperation, + final String prefixAddress, final int prefixLength) { + final Nat64PrefixEventParcel event = new Nat64PrefixEventParcel(); + event.netId = netId; + event.prefixOperation = prefixOperation; + event.prefixAddress = prefixAddress; + event.prefixLength = prefixLength; + return event; + } + @Test public void testStackedLinkProperties() throws Exception { final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24"); @@ -7856,7 +7899,6 @@ public class ConnectivityServiceTest { cellLp.addRoute(defaultRoute); cellLp.addRoute(ipv6Subnet); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); - reset(mNetworkManagementService); reset(mMockDnsResolver); reset(mMockNetd); reset(mBatteryStatsService); @@ -7896,7 +7938,6 @@ public class ConnectivityServiceTest { verifyNoMoreInteractions(mMockNetd); verifyNoMoreInteractions(mMockDnsResolver); - reset(mNetworkManagementService); reset(mMockNetd); reset(mMockDnsResolver); when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME)) @@ -7912,8 +7953,8 @@ public class ConnectivityServiceTest { // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started. Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix()); - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, - kNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); LinkProperties lpBeforeClat = networkCallback.expectCallback( CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp(); assertEquals(0, lpBeforeClat.getStackedLinks().size()); @@ -7953,8 +7994,8 @@ public class ConnectivityServiceTest { .thenReturn(getClatInterfaceConfigParcel(myIpv4)); // Change the NAT64 prefix without first removing it. // Expect clatd to be stopped and started with the new prefix. - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, - kOtherNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_ADDED, kOtherNat64PrefixString, 96)); networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, (lp) -> lp.getStackedLinks().size() == 0); verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); @@ -7996,15 +8037,14 @@ public class ConnectivityServiceTest { verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); verifyNoMoreInteractions(mMockNetd); verifyNoMoreInteractions(mMockDnsResolver); - reset(mNetworkManagementService); reset(mMockNetd); reset(mMockDnsResolver); when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME)) .thenReturn(getClatInterfaceConfigParcel(myIpv4)); // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, - kOtherNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_REMOVED, kOtherNat64PrefixString, 96)); networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, (lp) -> lp.getNat64Prefix() == null); @@ -8016,8 +8056,8 @@ public class ConnectivityServiceTest { networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); assertRoutesRemoved(cellNetId, ipv4Subnet); // Directly-connected routes auto-added. verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, - kNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); @@ -8029,8 +8069,8 @@ public class ConnectivityServiceTest { verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); // NAT64 prefix is removed. Expect that clat is stopped. - mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, - kNat64PrefixString, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_REMOVED, kNat64PrefixString, 96)); networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null); assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault); @@ -8118,8 +8158,8 @@ public class ConnectivityServiceTest { inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); - mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, - pref64FromDnsStr, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); @@ -8152,8 +8192,8 @@ public class ConnectivityServiceTest { inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); // Stopping prefix discovery results in a prefix removed notification. - mService.mNetdEventCallback.onNat64PrefixEvent(netId, false /* added */, - pref64FromDnsStr, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_REMOVED, pref64FromDnsStr, 96)); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); @@ -8191,8 +8231,8 @@ public class ConnectivityServiceTest { inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); - mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, - pref64FromDnsStr, 96); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any()); @@ -8233,7 +8273,6 @@ public class ConnectivityServiceTest { final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); mCellNetworkAgent.sendLinkProperties(cellLp); - reset(mNetworkManagementService); mCellNetworkAgent.connect(true); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(), @@ -8927,8 +8966,8 @@ public class ConnectivityServiceTest { 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, null, - 0, INVALID_UID, mQosCallbackTracker); + nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0, + INVALID_UID, mQosCallbackTracker); } @Test @@ -9256,7 +9295,7 @@ public class ConnectivityServiceTest { private void assertUidRangesUpdatedForMyUid(boolean add) throws Exception { final int uid = Process.myUid(); - assertVpnUidRangesUpdated(add, uidRangesForUid(uid), uid); + assertVpnUidRangesUpdated(add, uidRangesForUids(uid), uid); } private void assertVpnUidRangesUpdated(boolean add, Set<UidRange> vpnRanges, int exemptUid) @@ -9467,6 +9506,10 @@ public class ConnectivityServiceTest { fail("TOO_MANY_REQUESTS never thrown"); } + private UidRange createUidRange(int userId) { + return UidRange.createForUser(UserHandle.of(userId)); + } + private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid) throws Exception { final ApplicationInfo applicationInfo = new ApplicationInfo(); @@ -9641,7 +9684,7 @@ public class ConnectivityServiceTest { } @Test - public void testOemNetworkRequestFactoryCorrectlySetsUids() + public void testOemNetworkRequestFactoryMultiplePrefsCorrectlySetsUids() throws Exception { // Arrange PackageManager mocks final String testPackageName2 = "com.google.apps.dialer"; @@ -9672,6 +9715,46 @@ public class ConnectivityServiceTest { } @Test + public void testOemNetworkRequestFactoryMultipleUsersCorrectlySetsUids() + throws Exception { + // Arrange users + final int secondUser = 10; + final UserHandle secondUserHandle = new UserHandle(secondUser); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); + + // Arrange PackageManager mocks + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + + // Build OemNetworkPreferences object + final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final List<ConnectivityService.NetworkRequestInfo> nris = + new ArrayList<>( + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( + pref)); + + // UIDs for all users and all managed packages should be present. + // Two users each with two packages. + final int expectedUidSize = 2; + final List<UidRange> uids = + new ArrayList<>(nris.get(0).mRequests.get(0).networkCapabilities.getUids()); + assertEquals(expectedUidSize, uids.size()); + + // Sort by uid to access nris by index + uids.sort(Comparator.comparingInt(uid -> uid.start)); + final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); + assertEquals(TEST_PACKAGE_UID, uids.get(0).start); + assertEquals(TEST_PACKAGE_UID, uids.get(0).stop); + assertEquals(secondUserTestPackageUid, uids.get(1).start); + assertEquals(secondUserTestPackageUid, uids.get(1).stop); + } + + @Test public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference() throws Exception { // Expectations @@ -9801,6 +9884,54 @@ public class ConnectivityServiceTest { assertEquals(expectedPerAppNetwork, defaultNetwork); assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size()); } + verifyMultipleDefaultCallbacks(expectedDefaultNetwork, expectedPerAppNetwork); + } + + /** + * Verify default callbacks for 'available' fire as expected. This will only run if + * registerDefaultNetworkCallbacks() was executed prior and will only be different if the + * setOemNetworkPreference() per-app API was used for the current process. + * @param expectedSystemDefault the expected network for the system default. + * @param expectedPerAppDefault the expected network for the current process's default. + */ + private void verifyMultipleDefaultCallbacks( + @NonNull final Network expectedSystemDefault, + @NonNull final Network expectedPerAppDefault) { + if (null != mSystemDefaultNetworkCallback && null != expectedSystemDefault + && mService.mNoServiceNetwork.network() != expectedSystemDefault) { + // getLastAvailableNetwork() is used as this method can be called successively with + // the same network to validate therefore expectAvailableThenValidatedCallbacks + // can't be used. + assertEquals(mSystemDefaultNetworkCallback.getLastAvailableNetwork(), + expectedSystemDefault); + } + if (null != mDefaultNetworkCallback && null != expectedPerAppDefault + && mService.mNoServiceNetwork.network() != expectedPerAppDefault) { + assertEquals(mDefaultNetworkCallback.getLastAvailableNetwork(), + expectedPerAppDefault); + } + } + + private void registerDefaultNetworkCallbacks() { + // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback() + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + mSystemDefaultNetworkCallback = new TestNetworkCallback(); + mDefaultNetworkCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); + } + + private void unregisterDefaultNetworkCallbacks() { + if (null != mDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mDefaultNetworkCallback); + } + if (null != mSystemDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback); + } } private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @@ -9828,7 +9959,7 @@ public class ConnectivityServiceTest { assertEquals(1, mService.mDefaultNetworkRequests.size()); final UidRangeParcel[] uidRanges = - toUidRangeStableParcels(uidRangesForUid(testPackageUid)); + toUidRangeStableParcels(uidRangesForUids(testPackageUid)); setupSetOemNetworkPreferenceForPreferenceTest( networkPrefToSetup, uidRanges, testPackageName); } @@ -9849,12 +9980,11 @@ public class ConnectivityServiceTest { .build(); // Act on ConnectivityService.setOemNetworkPreference() - final TestOemListenerCallback mOnSetOemNetworkPreferenceTestListener = - new TestOemListenerCallback(); - mService.setOemNetworkPreference(pref, mOnSetOemNetworkPreferenceTestListener); + final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); + mService.setOemNetworkPreference(pref, oemPrefListener); // Verify call returned successfully - mOnSetOemNetworkPreferenceTestListener.expectOnComplete(); + oemPrefListener.expectOnComplete(); } private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener { @@ -9884,6 +10014,7 @@ public class ConnectivityServiceTest { @OemNetworkPreferences.OemNetworkPreference final int networkPref = OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -9898,6 +10029,7 @@ public class ConnectivityServiceTest { // Verify that the active network is correct verifyActiveNetwork(TRANSPORT_ETHERNET); + // default NCs will be unregistered in tearDown } @Test @@ -9905,6 +10037,7 @@ public class ConnectivityServiceTest { @OemNetworkPreferences.OemNetworkPreference final int networkPref = OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -9925,6 +10058,7 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.getNetwork()); assertFalse(mCm.isActiveNetworkMetered()); + // default NCs will be unregistered in tearDown } @Test @@ -10055,6 +10189,10 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(defaultNetworkCallback); } + /** + * This method assumes that the same uidRanges input will be used to verify that dependencies + * are called as expected. + */ private void verifySetOemNetworkPreferenceForPreference( @NonNull final UidRangeParcel[] uidRanges, final int addUidRangesNetId, @@ -10062,16 +10200,30 @@ public class ConnectivityServiceTest { final int removeUidRangesNetId, final int removeUidRangesTimes, final boolean shouldDestroyNetwork) throws RemoteException { + verifySetOemNetworkPreferenceForPreference(uidRanges, uidRanges, + addUidRangesNetId, addUidRangesTimes, removeUidRangesNetId, removeUidRangesTimes, + shouldDestroyNetwork); + } + + private void verifySetOemNetworkPreferenceForPreference( + @NonNull final UidRangeParcel[] addedUidRanges, + @NonNull final UidRangeParcel[] removedUidRanges, + final int addUidRangesNetId, + final int addUidRangesTimes, + final int removeUidRangesNetId, + final int removeUidRangesTimes, + final boolean shouldDestroyNetwork) throws RemoteException { final boolean useAnyIdForAdd = OEM_PREF_ANY_NET_ID == addUidRangesNetId; final boolean useAnyIdForRemove = OEM_PREF_ANY_NET_ID == removeUidRangesNetId; // Validate netd. verify(mMockNetd, times(addUidRangesTimes)) .networkAddUidRanges( - (useAnyIdForAdd ? anyInt() : eq(addUidRangesNetId)), eq(uidRanges)); + (useAnyIdForAdd ? anyInt() : eq(addUidRangesNetId)), eq(addedUidRanges)); verify(mMockNetd, times(removeUidRangesTimes)) .networkRemoveUidRanges( - (useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId)), eq(uidRanges)); + (useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId)), + eq(removedUidRanges)); if (shouldDestroyNetwork) { verify(mMockNetd, times(1)) .networkDestroy((useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId))); @@ -10081,7 +10233,6 @@ public class ConnectivityServiceTest { /** * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference(). - * @throws Exception */ @Test public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception { @@ -10090,7 +10241,7 @@ public class ConnectivityServiceTest { final int testPackageUid = 123; final String testPackageName = "com.google.apps.contacts"; final UidRangeParcel[] uidRanges = - toUidRangeStableParcels(uidRangesForUid(testPackageUid)); + toUidRangeStableParcels(uidRangesForUids(testPackageUid)); // Validate the starting requests only includes the fallback request. assertEquals(1, mService.mDefaultNetworkRequests.size()); @@ -10109,9 +10260,8 @@ public class ConnectivityServiceTest { } /** - * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID following in order: + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly() @@ -10120,9 +10270,8 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PAID; // Arrange PackageManager mocks - final int testPackageNameUid = 123; final UidRangeParcel[] uidRanges = - toUidRangeStableParcels(uidRangesForUid(testPackageNameUid)); + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); // Verify the starting state. No networks should be connected. @@ -10177,9 +10326,8 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly() @@ -10188,9 +10336,8 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; // Arrange PackageManager mocks - final int testPackageNameUid = 123; final UidRangeParcel[] uidRanges = - toUidRangeStableParcels(uidRangesForUid(testPackageNameUid)); + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); // Verify the starting state. This preference doesn't support using the fallback network @@ -10240,10 +10387,9 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: * NET_CAPABILITY_OEM_PAID * This preference should only apply to OEM_PAID networks. - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly() @@ -10252,9 +10398,8 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; // Arrange PackageManager mocks - final int testPackageNameUid = 123; final UidRangeParcel[] uidRanges = - toUidRangeStableParcels(uidRangesForUid(testPackageNameUid)); + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); // Verify the starting state. This preference doesn't support using the fallback network @@ -10294,10 +10439,9 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: * NET_CAPABILITY_OEM_PRIVATE * This preference should only apply to OEM_PRIVATE networks. - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly() @@ -10306,9 +10450,8 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; // Arrange PackageManager mocks - final int testPackageNameUid = 123; final UidRangeParcel[] uidRanges = - toUidRangeStableParcels(uidRangesForUid(testPackageNameUid)); + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); // Verify the starting state. This preference doesn't support using the fallback network @@ -10347,7 +10490,417 @@ public class ConnectivityServiceTest { true /* shouldDestroyNetwork */); } - private UidRange createUidRange(int userId) { - return UidRange.createForUser(UserHandle.of(userId)); + @Test + public void testMultilayerForMultipleUsersEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + + // Arrange users + final int secondUser = 10; + final UserHandle secondUserHandle = new UserHandle(secondUser); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); + + // Arrange PackageManager mocks + final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels( + uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid)); + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + + // Verify the starting state. No networks should be connected. + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test that we correctly add the expected values for multiple users. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test that we correctly remove the expected values for multiple users. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + mCellNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + } + + @Test + public void testMultilayerForBroadcastedUsersEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + + // Arrange users + final int secondUser = 10; + final UserHandle secondUserHandle = new UserHandle(secondUser); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE)); + + // Arrange PackageManager mocks + final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); + final UidRangeParcel[] uidRangesSingleUser = + toUidRangeStableParcels( + uidRangesForUids(TEST_PACKAGE_UID)); + final UidRangeParcel[] uidRangesBothUsers = + toUidRangeStableParcels( + uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid)); + setupSetOemNetworkPreferenceForPreferenceTest( + networkPref, uidRangesSingleUser, TEST_PACKAGE_NAME); + + // Verify the starting state. No networks should be connected. + verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test that we correctly add the expected values for multiple users. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Send a broadcast indicating a user was added. + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); + final Intent addedIntent = new Intent(ACTION_USER_ADDED); + addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser)); + processBroadcast(addedIntent); + + // Test that we correctly add values for all users and remove for the single user. + verifySetOemNetworkPreferenceForPreference(uidRangesBothUsers, uidRangesSingleUser, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + + // Send a broadcast indicating a user was removed. + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE)); + final Intent removedIntent = new Intent(ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser)); + processBroadcast(removedIntent); + + // Test that we correctly add values for the single user and remove for the all users. + verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, uidRangesBothUsers, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + } + + /** + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 3; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mCellNetworkAgent.getNetwork()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will put both on null as it is the last network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + null); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidNoFallbackCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 2; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network but not the pref. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: + * NET_CAPABILITY_OEM_PAID + * This preference should only apply to OEM_PAID networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID. + // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and the pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: + * NET_CAPABILITY_OEM_PRIVATE + * This preference should only apply to OEM_PRIVATE networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPrivateOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE. + startOemManagedNetwork(false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PRIVATE will keep the fallback on cellular. + // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network. + stopOemManagedNetwork(); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } + + @Test + public void testGetAllNetworkStateSnapshot() throws Exception { + verifyNoNetwork(); + + // Setup test cellular network with specified LinkProperties and NetworkCapabilities, + // verify the content of the snapshot matches. + final LinkProperties cellLp = new LinkProperties(); + final LinkAddress myIpv4Addr = new LinkAddress(InetAddress.getByName("192.0.2.129"), 25); + final LinkAddress myIpv6Addr = new LinkAddress(InetAddress.getByName("2001:db8::1"), 64); + cellLp.setInterfaceName("test01"); + cellLp.addLinkAddress(myIpv4Addr); + cellLp.addLinkAddress(myIpv6Addr); + cellLp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + cellLp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + cellLp.addRoute(new RouteInfo(myIpv4Addr, null)); + cellLp.addRoute(new RouteInfo(myIpv6Addr, null)); + final NetworkCapabilities cellNcTemplate = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_MMS).build(); + + final TestNetworkCallback cellCb = new TestNetworkCallback(); + mCm.requestNetwork(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(), + cellCb); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp, cellNcTemplate); + mCellNetworkAgent.connect(true); + cellCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + List<NetworkStateSnapshot> snapshots = mCm.getAllNetworkStateSnapshot(); + assertLength(1, snapshots); + + // Compose the expected cellular snapshot for verification. + final NetworkCapabilities cellNc = + mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()); + final NetworkStateSnapshot cellSnapshot = new NetworkStateSnapshot( + mCellNetworkAgent.getNetwork(), cellNc, cellLp, + null, ConnectivityManager.TYPE_MOBILE); + assertEquals(cellSnapshot, snapshots.get(0)); + + // Connect wifi and verify the snapshots. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + // Compose the expected wifi snapshot for verification. + final NetworkCapabilities wifiNc = + mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()); + final NetworkStateSnapshot wifiSnapshot = new NetworkStateSnapshot( + mWiFiNetworkAgent.getNetwork(), wifiNc, new LinkProperties(), null, + ConnectivityManager.TYPE_WIFI); + + snapshots = mCm.getAllNetworkStateSnapshot(); + assertLength(2, snapshots); + assertContainsAll(snapshots, cellSnapshot, wifiSnapshot); + + // Set cellular as suspended, verify the snapshots will not contain suspended networks. + // TODO: Consider include SUSPENDED networks, which should be considered as + // temporary shortage of connectivity of a connected network. + mCellNetworkAgent.suspend(); + waitForIdle(); + snapshots = mCm.getAllNetworkStateSnapshot(); + assertLength(1, snapshots); + assertEquals(wifiSnapshot, snapshots.get(0)); + + // Disconnect wifi, verify the snapshots contain nothing. + mWiFiNetworkAgent.disconnect(); + waitForIdle(); + snapshots = mCm.getAllNetworkStateSnapshot(); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertLength(0, snapshots); + + mCellNetworkAgent.resume(); + waitForIdle(); + snapshots = mCm.getAllNetworkStateSnapshot(); + assertLength(1, snapshots); + assertEquals(cellSnapshot, snapshots.get(0)); + + mCellNetworkAgent.disconnect(); + waitForIdle(); + verifyNoNetwork(); + mCm.unregisterNetworkCallback(cellCb); } } diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java index f5b85ca06f92..5760211d9a27 100644 --- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -22,6 +22,8 @@ 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; @@ -164,7 +166,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE, - InetAddress.parseNumericAddress("4.4.4.4"), "", true)); + InetAddress.parseNumericAddress("4.4.4.4"), "", + VALIDATION_RESULT_SUCCESS)); LinkProperties fixedLp = new LinkProperties(lp); mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); assertFalse(fixedLp.isPrivateDnsActive()); @@ -204,7 +207,8 @@ public class DnsManagerTest { // Validate one. mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com", true)); + InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com", + VALIDATION_RESULT_SUCCESS)); fixedLp = new LinkProperties(lp); mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); assertEquals(Arrays.asList(InetAddress.parseNumericAddress("6.6.6.6")), @@ -212,7 +216,8 @@ public class DnsManagerTest { // Validate the 2nd one. mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com", true)); + InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com", + VALIDATION_RESULT_SUCCESS)); fixedLp = new LinkProperties(lp); mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); assertEquals(Arrays.asList( @@ -232,7 +237,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -245,7 +251,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -253,7 +260,8 @@ public class DnsManagerTest { // Validation event has untracked ipAddress mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("4.4.4.4"), "", true)); + InetAddress.parseNumericAddress("4.4.4.4"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -261,8 +269,8 @@ public class DnsManagerTest { // Validation event has untracked hostname mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "hostname", - true)); + InetAddress.parseNumericAddress("3.3.3.3"), "hostname", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -270,7 +278,8 @@ public class DnsManagerTest { // Validation event failed mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", false)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_FAILURE)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -279,7 +288,7 @@ public class DnsManagerTest { mDnsManager.removeNetwork(new Network(TEST_NETID)); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -293,7 +302,8 @@ public class DnsManagerTest { mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, - InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); assertFalse(lp.isPrivateDnsActive()); assertNull(lp.getPrivateDnsServerName()); @@ -398,7 +408,8 @@ public class DnsManagerTest { mDnsManager.updatePrivateDns(network, mDnsManager.getPrivateDnsConfig()); mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); mDnsManager.updatePrivateDnsValidation( - new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "", true)); + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "", + VALIDATION_RESULT_SUCCESS)); mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); assertTrue(privateDnsCfg.useTls); diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 52cb836e19c8..a913673c2a1e 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -41,7 +41,6 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkProvider; import android.os.Binder; -import android.os.INetworkManagementService; import android.text.format.DateUtils; import androidx.test.filters.SmallTest; @@ -74,7 +73,6 @@ public class LingerMonitorTest { @Mock ConnectivityService mConnService; @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; - @Mock INetworkManagementService mNMS; @Mock Context mCtx; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; @@ -358,8 +356,8 @@ public class LingerMonitorTest { caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */, - mConnService, mNetd, mDnsResolver, mNMS, NetworkProvider.ID_NONE, - Binder.getCallingUid(), mQosCallbackTracker); + mConnService, mNetd, mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), + mQosCallbackTracker); nai.everValidated = true; return nai; } diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index 4f65b67fa3da..5f56e25356c2 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -36,7 +36,6 @@ import android.net.LinkProperties; import android.net.NetworkAgentConfig; import android.net.NetworkInfo; import android.os.Handler; -import android.os.INetworkManagementService; import android.os.test.TestLooper; import androidx.test.filters.SmallTest; @@ -67,7 +66,6 @@ public class Nat464XlatTest { @Mock ConnectivityService mConnectivity; @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; - @Mock INetworkManagementService mNms; @Mock NetworkAgentInfo mNai; TestLooper mLooper; @@ -75,7 +73,7 @@ public class Nat464XlatTest { NetworkAgentConfig mAgentConfig = new NetworkAgentConfig(); Nat464Xlat makeNat464Xlat() { - return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { + return new Nat464Xlat(mNai, mNetd, mDnsResolver) { @Override protected int getNetId() { return NETID; } @@ -206,7 +204,6 @@ public class Nat464XlatTest { // Start clat. nat.start(); - verify(mNms).registerObserver(eq(nat)); verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // Stacked interface up notification arrives. @@ -225,7 +222,6 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); - verify(mNms).unregisterObserver(eq(nat)); assertTrue(c.getValue().getStackedLinks().isEmpty()); assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); @@ -235,7 +231,7 @@ public class Nat464XlatTest { nat.interfaceRemoved(STACKED_IFACE); mLooper.dispatchNext(); - verifyNoMoreInteractions(mNetd, mNms, mConnectivity); + verifyNoMoreInteractions(mNetd, mConnectivity); } @Test @@ -346,7 +342,6 @@ public class Nat464XlatTest { nat.start(); - verify(mNms).registerObserver(eq(nat)); verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // Stacked interface up notification arrives. @@ -365,7 +360,6 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); - verify(mNms).unregisterObserver(eq(nat)); verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertTrue(c.getValue().getStackedLinks().isEmpty()); assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); @@ -374,7 +368,7 @@ public class Nat464XlatTest { // ConnectivityService stops clat: no-op. nat.stop(); - verifyNoMoreInteractions(mNetd, mNms, mConnectivity); + verifyNoMoreInteractions(mNetd, mConnectivity); } private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception { @@ -386,7 +380,6 @@ public class Nat464XlatTest { nat.start(); - verify(mNms).registerObserver(eq(nat)); verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) @@ -394,7 +387,6 @@ public class Nat464XlatTest { nat.stop(); verify(mNetd).clatdStop(eq(BASE_IFACE)); - verify(mNms).unregisterObserver(eq(nat)); verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); @@ -408,7 +400,7 @@ public class Nat464XlatTest { assertIdle(nat); - verifyNoMoreInteractions(mNetd, mNms, mConnectivity); + verifyNoMoreInteractions(mNetd, mConnectivity); } @Test @@ -430,7 +422,6 @@ public class Nat464XlatTest { nat.start(); - verify(mNms).registerObserver(eq(nat)); verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) @@ -438,11 +429,10 @@ public class Nat464XlatTest { nat.stop(); verify(mNetd).clatdStop(eq(BASE_IFACE)); - verify(mNms).unregisterObserver(eq(nat)); verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); - verifyNoMoreInteractions(mNetd, mNms, mConnectivity); + verifyNoMoreInteractions(mNetd, mConnectivity); } @Test diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 7489a0f889dc..b8f7fbca3983 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -91,7 +91,6 @@ import android.os.UserManager; import android.os.test.TestLooper; import android.provider.Settings; import android.security.Credentials; -import android.security.KeyStore; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Range; @@ -196,7 +195,7 @@ public class VpnTest { @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; @Mock private ConnectivityManager mConnectivityManager; @Mock private IpSecService mIpSecService; - @Mock private KeyStore mKeyStore; + @Mock private VpnProfileStore mVpnProfileStore; private final VpnProfile mVpnProfile; private IpSecManager mIpSecManager; @@ -333,17 +332,17 @@ public class VpnTest { assertFalse(vpn.getLockdown()); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertTrue(vpn.getLockdown()); // Remove always-on configuration. - assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList())); assertFalse(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); } @@ -354,17 +353,17 @@ public class VpnTest { final UidRange user = PRI_USER_RANGE; // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null)); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null)); verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) })); // Switch to another app. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) @@ -382,14 +381,14 @@ public class VpnTest { // Set always-on with lockdown and allow app PKGS[2] from lockdown. assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore)); + PKGS[1], true, Collections.singletonList(PKGS[2]))); verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop) })); // Change allowed app list to PKGS[3]. assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore)); + PKGS[1], true, Collections.singletonList(PKGS[3]))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop) })); @@ -400,7 +399,7 @@ public class VpnTest { // Change the VPN app. assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore)); + PKGS[0], true, Collections.singletonList(PKGS[3]))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1) @@ -411,7 +410,7 @@ public class VpnTest { })); // Remove the list of allowed packages. - assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null)); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1), new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop) @@ -422,7 +421,7 @@ public class VpnTest { // Add the list of allowed packages. assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore)); + PKGS[0], true, Collections.singletonList(PKGS[1]))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop) })); @@ -433,12 +432,12 @@ public class VpnTest { // Try allowing a package with a comma, should be rejected. assertFalse(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore)); + PKGS[0], true, Collections.singletonList("a.b,c.d"))); // Pass a non-existent packages in the allowlist, they (and only they) should be ignored. // allowed package should change from PGKS[1] to PKGS[2]. assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore)); + PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"))); verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) @@ -525,22 +524,22 @@ public class VpnTest { .thenReturn(Collections.singletonList(resInfo)); // null package name should return false - assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore)); + assertFalse(vpn.isAlwaysOnPackageSupported(null)); // Pre-N apps are not supported appInfo.targetSdkVersion = VERSION_CODES.M; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); // N+ apps are supported by default appInfo.targetSdkVersion = VERSION_CODES.N; - assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); + assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0])); // Apps that opt out explicitly are not supported appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; Bundle metaData = new Bundle(); metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false); svcInfo.metaData = metaData; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); } @Test @@ -556,7 +555,7 @@ public class VpnTest { order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt()); // Start showing a notification for disconnected once always-on. - vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore); + vpn.setAlwaysOnPackage(PKGS[0], false, null); order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); // Stop showing the notification once connected. @@ -568,7 +567,7 @@ public class VpnTest { order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); // Notification should be cleared after unsetting always-on package. - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + vpn.setAlwaysOnPackage(null, false, null); order.verify(mNotificationManager).cancel(anyString(), anyInt()); } @@ -608,15 +607,13 @@ public class VpnTest { } private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) { - assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore)); + assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile)); // The profile should always be stored, whether or not consent has been previously granted. - verify(mKeyStore) + verify(mVpnProfileStore) .put( eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), - eq(mVpnProfile.encode()), - eq(Process.SYSTEM_UID), - eq(0)); + eq(mVpnProfile.encode())); for (final String checkedOpStr : checkedOps) { verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG, @@ -671,7 +668,7 @@ public class VpnTest { bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]); try { - vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile, mKeyStore); + vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile); fail("Expected IAE due to profile size"); } catch (IllegalArgumentException expected) { } @@ -684,7 +681,7 @@ public class VpnTest { restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { - vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore); + vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile); fail("Expected SecurityException due to restricted user"); } catch (SecurityException expected) { } @@ -694,10 +691,10 @@ public class VpnTest { public void testDeleteVpnProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); - vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.deleteVpnProfile(TEST_VPN_PKG); - verify(mKeyStore) - .delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID)); + verify(mVpnProfileStore) + .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); } @Test @@ -707,7 +704,7 @@ public class VpnTest { restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { - vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.deleteVpnProfile(TEST_VPN_PKG); fail("Expected SecurityException due to restricted user"); } catch (SecurityException expected) { } @@ -717,24 +714,24 @@ public class VpnTest { public void testGetVpnProfilePrivileged() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(new VpnProfile("").encode()); - vpn.getVpnProfilePrivileged(TEST_VPN_PKG, mKeyStore); + vpn.getVpnProfilePrivileged(TEST_VPN_PKG); - verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); } @Test public void testStartVpnProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); - verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); verify(mAppOps) .noteOpNoThrow( eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), @@ -748,10 +745,10 @@ public class VpnTest { public void testStartVpnProfileVpnServicePreconsented() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown. verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(), @@ -763,7 +760,7 @@ public class VpnTest { final Vpn vpn = createVpnAndSetupUidChecks(); try { - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); fail("Expected failure due to no user consent"); } catch (SecurityException expected) { } @@ -780,22 +777,22 @@ public class VpnTest { TEST_VPN_PKG, null /* attributionTag */, null /* message */); // Keystore should never have been accessed. - verify(mKeyStore, never()).get(any()); + verify(mVpnProfileStore, never()).get(any()); } @Test public void testStartVpnProfileMissingProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); try { - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); fail("Expected failure due to missing profile"); } catch (IllegalArgumentException expected) { } - verify(mKeyStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); + verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); verify(mAppOps) .noteOpNoThrow( eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), @@ -812,7 +809,7 @@ public class VpnTest { restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { - vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + vpn.startVpnProfile(TEST_VPN_PKG); fail("Expected SecurityException due to restricted user"); } catch (SecurityException expected) { } @@ -938,9 +935,9 @@ public class VpnTest { } private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) { - assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore)); + assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null)); - verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); verify(mAppOps).setMode( eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG), eq(AppOpsManager.MODE_ALLOWED)); @@ -963,11 +960,11 @@ public class VpnTest { final int uid = Process.myUid() + 1; when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) .thenReturn(uid); - when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); setAndVerifyAlwaysOnPackage(vpn, uid, false); - assertTrue(vpn.startAlwaysOnVpn(mKeyStore)); + assertTrue(vpn.startAlwaysOnVpn()); // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in // a subsequent CL. @@ -984,7 +981,7 @@ public class VpnTest { InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE); lp.addRoute(defaultRoute); - vpn.startLegacyVpn(vpnProfile, mKeyStore, EGRESS_NETWORK, lp); + vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp); return vpn; } @@ -1186,7 +1183,7 @@ public class VpnTest { .thenReturn(asUserContext); final TestLooper testLooper = new TestLooper(); final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService, - mNetd, userId, mKeyStore, mSystemServices, mIkev2SessionCreator); + mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator); verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat( provider -> provider.getName().contains("VpnNetworkProvider") )); diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 54d6fb9f2c12..9334e2c4ad77 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -19,9 +19,7 @@ package com.android.server.net; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkIdentity.OEM_PAID; import static android.net.NetworkIdentity.OEM_PRIVATE; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; @@ -86,7 +84,7 @@ import android.net.INetworkStatsSession; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; @@ -286,7 +284,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -329,7 +327,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -403,7 +401,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -444,7 +442,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testUidStatsAcrossNetworks() throws Exception { // pretend first mobile network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -475,7 +473,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // disappearing, to verify we don't count backwards. incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); - states = new NetworkState[] {buildMobile3gState(IMSI_2)}; + states = new NetworkStateSnapshot[] {buildMobile3gState(IMSI_2)}; expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) @@ -519,7 +517,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testUidRemovedIsMoved() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -583,7 +581,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_LTE); final NetworkTemplate template5g = buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_NR); - final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; // 3G network comes online. expectNetworkStatsSummary(buildEmptyStats()); @@ -673,7 +672,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_NO); // OEM_PAID network comes online. - NetworkState[] states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, + NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{ + buildOemManagedMobileState(IMSI_1, false, new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PAID})}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -688,7 +688,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { forcePollAndWaitForIdle(); // OEM_PRIVATE network comes online. - states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE})}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -703,7 +703,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { forcePollAndWaitForIdle(); // OEM_PAID + OEM_PRIVATE network comes online. - states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, NetworkCapabilities.NET_CAPABILITY_OEM_PAID})}; expectNetworkStatsSummary(buildEmptyStats()); @@ -719,7 +719,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { forcePollAndWaitForIdle(); // OEM_NONE network comes online. - states = new NetworkState[]{buildOemManagedMobileState(IMSI_1, false, new int[]{})}; + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{})}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), @@ -771,7 +771,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testSummaryForAllUid() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -830,7 +830,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testDetailedUidStats() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -871,9 +871,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { final String stackedIface = "stacked-test0"; final LinkProperties stackedProp = new LinkProperties(); stackedProp.setInterfaceName(stackedIface); - final NetworkState wifiState = buildWifiState(); + final NetworkStateSnapshot wifiState = buildWifiState(); wifiState.linkProperties.addStackedLink(stackedProp); - NetworkState[] states = new NetworkState[] {wifiState}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {wifiState}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -929,7 +929,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testForegroundBackground() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -986,8 +986,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testMetered() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = - new NetworkState[] {buildWifiState(true /* isMetered */, TEST_IFACE)}; + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[] {buildWifiState(true /* isMetered */, TEST_IFACE)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1026,8 +1026,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testRoaming() throws Exception { // pretend that network comes online expectDefaultSettings(); - NetworkState[] states = - new NetworkState[] {buildMobile3gState(IMSI_1, true /* isRoaming */)}; + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[] {buildMobile3gState(IMSI_1, true /* isRoaming */)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1065,7 +1065,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testTethering() throws Exception { // pretend first mobile network comes online expectDefaultSettings(); - final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1122,7 +1123,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildWifiState()}; + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1220,8 +1221,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testStatsProviderUpdateStats() throws Exception { // Pretend that network comes online. expectDefaultSettings(); - final NetworkState[] states = - new NetworkState[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1282,8 +1283,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testStatsProviderSetAlert() throws Exception { // Pretend that network comes online. expectDefaultSettings(); - NetworkState[] states = - new NetworkState[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new UnderlyingNetworkInfo[0]); @@ -1326,7 +1327,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UNKNOWN); final NetworkTemplate templateAll = buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL); - final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1401,7 +1403,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testOperationCount_nonDefault_traffic() throws Exception { // Pretend mobile network comes online, but wifi is the default network. expectDefaultSettings(); - NetworkState[] states = new NetworkState[]{ + NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{ buildWifiState(true /*isMetered*/, TEST_IFACE2), buildMobile3gState(IMSI_1)}; expectNetworkStatsUidDetail(buildEmptyStats()); mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), @@ -1489,7 +1491,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(buildEmptyStats()); } - private String getActiveIface(NetworkState... states) throws Exception { + private String getActiveIface(NetworkStateSnapshot... states) throws Exception { if (states == null || states.length == 0 || states[0].linkProperties == null) { return null; } @@ -1565,11 +1567,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { assertEquals("unexpected operations", operations, entry.operations); } - private static NetworkState buildWifiState() { + private static NetworkStateSnapshot buildWifiState() { return buildWifiState(false, TEST_IFACE); } - private static NetworkState buildWifiState(boolean isMetered, @NonNull String iface) { + private static NetworkStateSnapshot buildWifiState(boolean isMetered, @NonNull String iface) { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(iface); final NetworkCapabilities capabilities = new NetworkCapabilities(); @@ -1577,35 +1579,30 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); capabilities.setSSID(TEST_SSID); - return new NetworkState(TYPE_WIFI, prop, capabilities, WIFI_NETWORK, null); + return new NetworkStateSnapshot(WIFI_NETWORK, capabilities, prop, null, TYPE_WIFI); } - private static NetworkState buildMobile3gState(String subscriberId) { + private static NetworkStateSnapshot buildMobile3gState(String subscriberId) { return buildMobile3gState(subscriberId, false /* isRoaming */); } - private static NetworkState buildMobile3gState(String subscriberId, boolean isRoaming) { + private static NetworkStateSnapshot buildMobile3gState(String subscriberId, boolean isRoaming) { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities capabilities = new NetworkCapabilities(); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming); capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - return new NetworkState(TYPE_MOBILE, prop, capabilities, MOBILE_NETWORK, subscriberId); + return new NetworkStateSnapshot( + MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE); } private NetworkStats buildEmptyStats() { return new NetworkStats(getElapsedRealtime(), 0); } - private static NetworkState buildVpnState() { - final LinkProperties prop = new LinkProperties(); - prop.setInterfaceName(TUN_IFACE); - return new NetworkState(TYPE_VPN, prop, new NetworkCapabilities(), VPN_NETWORK, null); - } - - private static NetworkState buildOemManagedMobileState(String subscriberId, boolean isRoaming, - int[] oemNetCapabilities) { + private static NetworkStateSnapshot buildOemManagedMobileState( + String subscriberId, boolean isRoaming, int[] oemNetCapabilities) { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities capabilities = new NetworkCapabilities(); @@ -1615,7 +1612,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { capabilities.setCapability(nc, true); } capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - return new NetworkState(TYPE_MOBILE, prop, capabilities, MOBILE_NETWORK, subscriberId); + return new NetworkStateSnapshot(MOBILE_NETWORK, capabilities, prop, subscriberId, + TYPE_MOBILE); } private long getElapsedRealtime() { diff --git a/tests/vcn/assets/self-signed-ca.pem b/tests/vcn/assets/self-signed-ca.pem new file mode 100644 index 000000000000..5135ea7077a8 --- /dev/null +++ b/tests/vcn/assets/self-signed-ca.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPjCCAiagAwIBAgIICrKLpR7LxlowDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxHDAaBgNVBAMTE2NhLnRlc3QuYW5kcm9p +ZC5uZXQwHhcNMTkwNzE2MTcxNTUyWhcNMjkwNzEzMTcxNTUyWjA9MQswCQYDVQQG +EwJVUzEQMA4GA1UEChMHQW5kcm9pZDEcMBoGA1UEAxMTY2EudGVzdC5hbmRyb2lk +Lm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANsvTwad2Nie0VOy +Xb1VtHL0R760Jm4vr14JWMcX4oiE6jUdTNdXQ0CGb65wvulP2aEeukFH0D/cvBMR +Bv9+haEwo9/grIXg9ALNKp+GfuZYw/dfnUMHFn3g2+SUgP6BoMZc4lkHktjkDKxp +99Q6h4NP/ip1labkhBeB9+Z6l78LTixKRKspNITWASJed9bjzshYxKHi6dJy3maQ +1LwYKmK7PEGRpoDoT8yZhFbxsVDUojGnJKH1RLXVOn/psG6dI/+IsbTipAttj5zc +g2VAD56PZG2Jd+vsup+g4Dy72hyy242x5c/H2LKZn4X0B0B+IXyii/ZVc+DJldQ5 +JqplOL8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFGYUzuvZUaVJl8mcxejuFiUNGcTfMA0GCSqGSIb3DQEBCwUAA4IB +AQDQYeqjvHsK2ZqSqxakDp0nu36Plbj48Wvx1ru7GW2faz7i0w/Zkxh06zniILCb +QJRjDebSTHc5SSbCFrRTvqagaLDhbH42/hQncWqIoJqW+pmznJET4JiBO0sqzm05 +yQWsLI/h9Ir28Y2g5N+XPBU0VVVejQqH4iI0iwQx7y7ABssQ0Xa/K73VPbeGaKd6 +Prt4wjJvTlIL2yE2+0MggJ3F2rNptL5SDpg3g+4/YQ6wVRBFil95kUqplEsCtU4P +t+8RghiEmsRx/8CywKfZ5Hex87ODhsSDmDApcefbd5gxoWVkqxZUkPcKwYv1ucm8 +u4r44fj4/9W0Zeooav5Yoh1q +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index 66590c92579b..7515971b8307 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -203,9 +203,6 @@ public class VcnManagerTest { IVcnStatusCallback cbBinder = new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback); - cbBinder.onEnteredSafeMode(); - verify(mMockStatusCallback).onEnteredSafeMode(); - cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java new file mode 100644 index 000000000000..bc8e9d3200b6 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.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 android.net.vcn.persistablebundleutils; + +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static org.junit.Assert.assertEquals; + +import android.net.eap.EapSessionConfig; +import android.os.PersistableBundle; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class EapSessionConfigUtilsTest { + private static final byte[] EAP_ID = "test@android.net".getBytes(StandardCharsets.US_ASCII); + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final int SUB_ID = 1; + private static final String NETWORK_NAME = "android.net"; + private static final boolean ALLOW_MISMATCHED_NETWORK_NAMES = true; + + private EapSessionConfig.Builder createBuilderWithId() { + return new EapSessionConfig.Builder().setEapIdentity(EAP_ID); + } + + private static void verifyPersistableBundleEncodeDecodeIsLossless(EapSessionConfig config) { + final PersistableBundle bundle = EapSessionConfigUtils.toPersistableBundle(config); + final EapSessionConfig resultConfig = EapSessionConfigUtils.fromPersistableBundle(bundle); + + assertEquals(config, resultConfig); + } + + @Test + public void testSetEapMsChapV2EncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId().setEapMsChapV2Config(USERNAME, PASSWORD).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapSimEncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId().setEapSimConfig(SUB_ID, APPTYPE_USIM).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapAkaEncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId().setEapAkaConfig(SUB_ID, APPTYPE_USIM).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapAkaPrimeEncodeDecodeIsLossless() throws Exception { + final EapSessionConfig config = + createBuilderWithId() + .setEapAkaPrimeConfig( + SUB_ID, APPTYPE_USIM, NETWORK_NAME, ALLOW_MISMATCHED_NETWORK_NAMES) + .build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } + + @Test + public void testSetEapTtlsEncodeDecodeIsLossless() throws Exception { + final InputStream inputStream = + InstrumentationRegistry.getContext() + .getResources() + .getAssets() + .open("self-signed-ca.pem"); + final CertificateFactory factory = CertificateFactory.getInstance("X.509"); + final X509Certificate trustedCa = + (X509Certificate) factory.generateCertificate(inputStream); + + final EapSessionConfig innerConfig = + new EapSessionConfig.Builder().setEapMsChapV2Config(USERNAME, PASSWORD).build(); + + final EapSessionConfig config = + new EapSessionConfig.Builder().setEapTtlsConfig(trustedCa, innerConfig).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(config); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java new file mode 100644 index 000000000000..4f3930f9b5af --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.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 android.net.vcn.persistablebundleutils; + +import static org.junit.Assert.assertEquals; + +import android.net.ipsec.ike.IkeDerAsn1DnIdentification; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeIdentification; +import android.net.ipsec.ike.IkeIpv4AddrIdentification; +import android.net.ipsec.ike.IkeIpv6AddrIdentification; +import android.net.ipsec.ike.IkeKeyIdIdentification; +import android.net.ipsec.ike.IkeRfc822AddrIdentification; +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +import javax.security.auth.x500.X500Principal; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IkeIdentificationUtilsTest { + private static void verifyPersistableBundleEncodeDecodeIsLossless(IkeIdentification id) { + final PersistableBundle bundle = IkeIdentificationUtils.toPersistableBundle(id); + final IkeIdentification result = IkeIdentificationUtils.fromPersistableBundle(bundle); + + assertEquals(result, id); + } + + @Test + public void testPersistableBundleEncodeDecodeIpv4AddressId() throws Exception { + final Inet4Address ipv4Address = (Inet4Address) InetAddress.getByName("192.0.2.100"); + verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv4AddrIdentification(ipv4Address)); + } + + @Test + public void testPersistableBundleEncodeDecodeIpv6AddressId() throws Exception { + final Inet6Address ipv6Address = (Inet6Address) InetAddress.getByName("2001:db8:2::100"); + verifyPersistableBundleEncodeDecodeIsLossless(new IkeIpv6AddrIdentification(ipv6Address)); + } + + @Test + public void testPersistableBundleEncodeDecodeRfc822AddrId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless(new IkeFqdnIdentification("ike.android.net")); + } + + @Test + public void testPersistableBundleEncodeDecodeFqdnId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeRfc822AddrIdentification("androidike@example.com")); + } + + @Test + public void testPersistableBundleEncodeDecodeKeyId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeKeyIdIdentification("androidIkeKeyId".getBytes())); + } + + @Test + public void testPersistableBundleEncodeDecodeDerAsn1DnId() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeDerAsn1DnIdentification( + new X500Principal("CN=small.server.test.android.net, O=Android, C=US"))); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.java new file mode 100644 index 000000000000..28cf38a2a583 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.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 android.net.vcn.persistablebundleutils; + +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IkeTrafficSelectorUtilsTest { + private static final int START_PORT = 16; + private static final int END_PORT = 65520; + + private static final InetAddress IPV4_START_ADDRESS = + InetAddresses.parseNumericAddress("192.0.2.100"); + private static final InetAddress IPV4_END_ADDRESS = + InetAddresses.parseNumericAddress("192.0.2.101"); + + private static final InetAddress IPV6_START_ADDRESS = + InetAddresses.parseNumericAddress("2001:db8:2::100"); + private static final InetAddress IPV6_END_ADDRESS = + InetAddresses.parseNumericAddress("2001:db8:2::101"); + + private static void verifyPersistableBundleEncodeDecodeIsLossless(IkeTrafficSelector ts) { + final PersistableBundle bundle = IkeTrafficSelectorUtils.toPersistableBundle(ts); + final IkeTrafficSelector resultTs = IkeTrafficSelectorUtils.fromPersistableBundle(bundle); + assertEquals(ts, resultTs); + } + + @Test + public void testPersistableBundleEncodeDecodeIsLosslessIpv4Ts() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeTrafficSelector(START_PORT, END_PORT, IPV4_START_ADDRESS, IPV4_END_ADDRESS)); + } + + @Test + public void testPersistableBundleEncodeDecodeIsLosslessIpv6Ts() throws Exception { + verifyPersistableBundleEncodeDecodeIsLossless( + new IkeTrafficSelector(START_PORT, END_PORT, IPV6_START_ADDRESS, IPV6_END_ADDRESS)); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java new file mode 100644 index 000000000000..8ae8692b4f75 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.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.net.vcn.persistablebundleutils; + +import static org.junit.Assert.assertEquals; + +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.SaProposal; +import android.os.PersistableBundle; + +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 SaProposalUtilsTest { + @Test + public void testPersistableBundleEncodeDecodeIsLosslessIkeProposal() throws Exception { + final IkeSaProposal proposal = + new IkeSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_3DES, SaProposal.KEY_LEN_UNUSED) + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .addDhGroup(SaProposal.DH_GROUP_3072_BIT_MODP) + .build(); + + final PersistableBundle bundle = IkeSaProposalUtils.toPersistableBundle(proposal); + final SaProposal resultProposal = IkeSaProposalUtils.fromPersistableBundle(bundle); + + assertEquals(proposal, resultProposal); + } + + /** Package private so that TunnelModeChildSessionParamsUtilsTest can use it */ + static ChildSaProposal buildTestChildSaProposal() { + return new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128) + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_192) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .addDhGroup(SaProposal.DH_GROUP_4096_BIT_MODP) + .build(); + } + + @Test + public void testPersistableBundleEncodeDecodeIsLosslessChildProposal() throws Exception { + final ChildSaProposal proposal = buildTestChildSaProposal(); + + final PersistableBundle bundle = ChildSaProposalUtils.toPersistableBundle(proposal); + final SaProposal resultProposal = ChildSaProposalUtils.fromPersistableBundle(bundle); + + assertEquals(proposal, resultProposal); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java new file mode 100644 index 000000000000..b3cd0ab80599 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vcn.persistablebundleutils; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeTrafficSelector; +import android.net.ipsec.ike.TunnelModeChildSessionParams; +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TunnelModeChildSessionParamsUtilsTest { + private TunnelModeChildSessionParams.Builder createBuilderMinimum() { + final ChildSaProposal saProposal = SaProposalUtilsTest.buildTestChildSaProposal(); + return new TunnelModeChildSessionParams.Builder().addSaProposal(saProposal); + } + + private static void verifyPersistableBundleEncodeDecodeIsLossless( + TunnelModeChildSessionParams params) { + final PersistableBundle bundle = + TunnelModeChildSessionParamsUtils.toPersistableBundle(params); + final TunnelModeChildSessionParams result = + TunnelModeChildSessionParamsUtils.fromPersistableBundle(bundle); + + assertEquals(params, result); + } + + @Test + public void testMinimumParamsEncodeDecodeIsLossless() throws Exception { + final TunnelModeChildSessionParams sessionParams = createBuilderMinimum().build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } + + @Test + public void testSetTsEncodeDecodeIsLossless() throws Exception { + final IkeTrafficSelector tsInbound = + new IkeTrafficSelector( + 16, + 65520, + InetAddresses.parseNumericAddress("192.0.2.100"), + InetAddresses.parseNumericAddress("192.0.2.101")); + final IkeTrafficSelector tsOutbound = + new IkeTrafficSelector( + 32, + 256, + InetAddresses.parseNumericAddress("192.0.2.200"), + InetAddresses.parseNumericAddress("192.0.2.255")); + + final TunnelModeChildSessionParams sessionParams = + createBuilderMinimum() + .addInboundTrafficSelectors(tsInbound) + .addOutboundTrafficSelectors(tsOutbound) + .build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } + + @Test + public void testSetLifetimesEncodeDecodeIsLossless() throws Exception { + final int hardLifetime = (int) TimeUnit.HOURS.toSeconds(3L); + final int softLifetime = (int) TimeUnit.HOURS.toSeconds(1L); + + final TunnelModeChildSessionParams sessionParams = + createBuilderMinimum().setLifetimeSeconds(hardLifetime, softLifetime).build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } + + @Test + public void testSetConfigRequestsEncodeDecodeIsLossless() throws Exception { + final int ipv6PrefixLen = 64; + final Inet4Address ipv4Address = + (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.100"); + final Inet6Address ipv6Address = + (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1"); + + final TunnelModeChildSessionParams sessionParams = + createBuilderMinimum() + .addInternalAddressRequest(AF_INET) + .addInternalAddressRequest(AF_INET6) + .addInternalAddressRequest(ipv4Address) + .addInternalAddressRequest(ipv6Address, ipv6PrefixLen) + .addInternalDnsServerRequest(AF_INET) + .addInternalDnsServerRequest(AF_INET6) + .addInternalDhcpServerRequest(AF_INET) + .build(); + verifyPersistableBundleEncodeDecodeIsLossless(sessionParams); + } +} diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 9b500a7271d7..73a6b88e29ed 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -100,6 +100,8 @@ import java.util.UUID; public class VcnManagementServiceTest { private static final String TEST_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName(); + private static final String TEST_CB_PACKAGE_NAME = + VcnManagementServiceTest.class.getPackage().getName() + ".callback"; private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0)); private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1)); private static final VcnConfig TEST_VCN_CONFIG; @@ -288,6 +290,14 @@ public class VcnManagementServiceTest { private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) { + return triggerSubscriptionTrackerCbAndGetSnapshot( + activeSubscriptionGroups, subIdToGroupMap, true /* hasCarrierPrivileges */); + } + + private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( + Set<ParcelUuid> activeSubscriptionGroups, + Map<Integer, ParcelUuid> subIdToGroupMap, + boolean hasCarrierPrivileges) { final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups(); @@ -295,7 +305,7 @@ public class VcnManagementServiceTest { (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty()) ? Collections.emptySet() : Collections.singleton(TEST_PACKAGE_NAME); - doReturn(true) + doReturn(hasCarrierPrivileges) .when(snapshot) .packageHasPermissionsForSubscriptionGroup( argThat(val -> activeSubscriptionGroups.contains(val)), @@ -549,13 +559,6 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } - private void setUpVcnSubscription(int subId, ParcelUuid subGroup) { - mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); - - triggerSubscriptionTrackerCbAndGetSnapshot( - Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup)); - } - private void verifyMergedNetworkCapabilities( NetworkCapabilities mergedCapabilities, @Transport int transportType, @@ -573,9 +576,23 @@ public class VcnManagementServiceTest { } private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) { - setUpVcnSubscription(subId, subGrp); + setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive, true /* hasCarrierPrivileges */); + } + + private void setupSubscriptionAndStartVcn( + int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) { + mVcnMgmtSvc.systemReady(); + triggerSubscriptionTrackerCbAndGetSnapshot( + Collections.singleton(subGrp), + Collections.singletonMap(subId, subGrp), + hasCarrierPrivileges); + final Vcn vcn = startAndGetVcnInstance(subGrp); doReturn(isVcnActive).when(vcn).isActive(); + + doReturn(true) + .when(mLocationPermissionChecker) + .checkLocationPermission(eq(TEST_PACKAGE_NAME), any(), eq(TEST_UID), any()); } private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport( @@ -721,7 +738,7 @@ public class VcnManagementServiceTest { verify(mMockPolicyListener).onPolicyChanged(); } - private void verifyVcnCallback( + private void triggerVcnSafeMode( @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot) throws Exception { verify(mMockDeps) @@ -732,20 +749,20 @@ public class VcnManagementServiceTest { eq(snapshot), mVcnCallbackCaptor.capture()); - mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); - VcnCallback vcnCallback = mVcnCallbackCaptor.getValue(); vcnCallback.onEnteredSafeMode(); - - verify(mMockPolicyListener).onPolicyChanged(); } @Test - public void testVcnCallbackOnEnteredSafeMode() throws Exception { + public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); - verifyVcnCallback(TEST_UUID_1, snapshot); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + triggerVcnSafeMode(TEST_UUID_1, snapshot); + + verify(mMockPolicyListener).onPolicyChanged(); } private void triggerVcnStatusCallbackOnEnteredSafeMode( @@ -758,6 +775,9 @@ public class VcnManagementServiceTest { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup)); + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, subGroup, true /* isActive */, hasPermissionsforSubGroup); + doReturn(hasPermissionsforSubGroup) .when(snapshot) .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName)); @@ -768,10 +788,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName); - // Trigger systemReady() to set up LocationPermissionChecker - mVcnMgmtSvc.systemReady(); - - verifyVcnCallback(subGroup, snapshot); + triggerVcnSafeMode(subGroup, snapshot); } @Test @@ -825,6 +842,83 @@ public class VcnManagementServiceTest { assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName); assertEquals(TEST_UID, cbInfo.mUid); verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt()); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); + } + + @Test + public void testRegisterVcnStatusCallback_MissingPermission() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + true /* isActive */, + false /* hasCarrierPrivileges */); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); + } + + @Test + public void testRegisterVcnStatusCallback_VcnInactive() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + true /* isActive */, + true /* hasCarrierPrivileges */); + + // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown + // timeout so the VCN goes inactive. + final TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot( + Collections.singleton(TEST_UUID_1), + Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1), + false /* hasCarrierPrivileges */); + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + + // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE + // when the status callback is registered). Instead, setup permissions for TEST_CB_PACKAGE + // so that it's permissioned to receive INACTIVE (instead of NOT_CONFIGURED) without + // reactivating the VCN. + doReturn(true) + .when(snapshot) + .packageHasPermissionsForSubscriptionGroup( + eq(TEST_UUID_1), eq(TEST_CB_PACKAGE_NAME)); + doReturn(true) + .when(mLocationPermissionChecker) + .checkLocationPermission(eq(TEST_CB_PACKAGE_NAME), any(), eq(TEST_UID), any()); + + mVcnMgmtSvc.registerVcnStatusCallback( + TEST_UUID_1, mMockStatusCallback, TEST_CB_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_INACTIVE); + } + + @Test + public void testRegisterVcnStatusCallback_VcnActive() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + true /* isActive */, + true /* hasCarrierPrivileges */); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE); + } + + @Test + public void testRegisterVcnStatusCallback_VcnSafeMode() throws Exception { + setupSubscriptionAndStartVcn( + TEST_SUBSCRIPTION_ID, + TEST_UUID_1, + false /* isActive */, + true /* hasCarrierPrivileges */); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test(expected = IllegalStateException.class) diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 69c21b967917..69b2fb135a8d 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -36,6 +36,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -143,11 +144,18 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); mTestLooper.dispatchAll(); + verify(mIpSecSvc, times(2)) + .setNetworkForTunnelInterface( + eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), + eq(TEST_UNDERLYING_NETWORK_RECORD_1.network), + any()); + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { verify(mIpSecSvc) .applyTunnelModeTransform( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); } + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); } @@ -290,4 +298,22 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback( new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR); } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertTrue(mGatewayConnection.isQuitting()); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index 17ae19e086cf..d07d2cf4f1bb 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -19,6 +19,8 @@ package com.android.server.vcn; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -111,4 +113,22 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio public void testSafeModeTimeoutNotifiesCallback() { verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState); } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertTrue(mGatewayConnection.isQuitting()); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 9ea641f52e48..5f27fabb62b0 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -21,9 +21,12 @@ import static android.net.IpSecManager.IpSecTunnelInterface; import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.net.IpSecManager; @@ -54,7 +57,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect } @Test - public void testEnterWhileNotRunningTriggersQuit() throws Exception { + public void testEnterWhileQuittingTriggersQuit() throws Exception { final VcnGatewayConnection vgc = new VcnGatewayConnection( mVcnContext, @@ -64,7 +67,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mGatewayStatusCallback, mDeps); - vgc.setIsRunning(false); + vgc.setIsQuitting(true); vgc.transitionTo(vgc.mDisconnectedState); mTestLooper.dispatchAll(); @@ -101,5 +104,18 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect assertNull(mGatewayConnection.getCurrentState()); verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any()); verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */); + assertTrue(mGatewayConnection.isQuitting()); + verify(mGatewayStatusCallback).onQuit(); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + verify(mGatewayStatusCallback, never()).onQuit(); + // No safe mode timer changes expected. } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java index 7385204993c0..661e03af4f84 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -18,6 +18,8 @@ package com.android.server.vcn; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -79,10 +81,20 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec // Should do nothing; already tearing down. assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); + assertTrue(mGatewayConnection.isQuitting()); } @Test public void testSafeModeTimeoutNotifiesCallback() { verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState); } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index 5b0850b03f1a..85a0277f8b48 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -17,6 +17,9 @@ package com.android.server.vcn; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -96,4 +99,22 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect public void testSafeModeTimeoutNotifiesCallback() { verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState); } + + @Test + public void testTeardownDisconnectRequest() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertNull(mGatewayConnection.getCurrentState()); + assertTrue(mGatewayConnection.isQuitting()); + } + + @Test + public void testNonTeardownDisconnectRequest() throws Exception { + mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + assertFalse(mGatewayConnection.isQuitting()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 9d3368271243..3dd710afed7b 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -16,6 +16,10 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.Matchers.any; @@ -33,6 +37,7 @@ import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; import android.os.test.TestLooper; +import android.util.ArraySet; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; @@ -51,6 +56,11 @@ public class VcnTest { private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final int NETWORK_SCORE = 0; private static final int PROVIDER_ID = 5; + private static final int[][] TEST_CAPS = + new int[][] { + new int[] {NET_CAPABILITY_INTERNET, NET_CAPABILITY_MMS}, + new int[] {NET_CAPABILITY_DUN} + }; private Context mContext; private VcnContext mVcnContext; @@ -91,13 +101,12 @@ public class VcnTest { mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class); final VcnConfig.Builder configBuilder = new VcnConfig.Builder(mContext); - for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { + for (final int[] caps : TEST_CAPS) { configBuilder.addGatewayConnectionConfig( - VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(capability)); + VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(caps)); } - configBuilder.addGatewayConnectionConfig(VcnGatewayConnectionConfigTest.buildTestConfig()); - mConfig = configBuilder.build(); + mConfig = configBuilder.build(); mVcn = new Vcn( mVcnContext, @@ -130,8 +139,7 @@ public class VcnTest { @Test public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() { final NetworkRequestListener requestListener = verifyAndGetRequestListener(); - startVcnGatewayWithCapabilities( - requestListener, VcnGatewayConnectionConfigTest.EXPOSED_CAPS); + startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]); final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); assertFalse(gatewayConnections.isEmpty()); @@ -153,10 +161,19 @@ public class VcnTest { for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { startVcnGatewayWithCapabilities(requestListener, capability); } + } + + private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { + for (final int[] caps : TEST_CAPS) { + startVcnGatewayWithCapabilities(requestListener, caps); + } + } - // Each Capability in EXPOSED_CAPS was split into a separate VcnGatewayConnection in #setUp. - // Expect one VcnGatewayConnection per capability. - final int numExpectedGateways = VcnGatewayConnectionConfigTest.EXPOSED_CAPS.length; + public Set<VcnGatewayConnection> startGatewaysAndGetGatewayConnections( + NetworkRequestListener requestListener) { + triggerVcnRequestListeners(requestListener); + + final int numExpectedGateways = TEST_CAPS.length; final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); assertEquals(numExpectedGateways, gatewayConnections.size()); @@ -168,7 +185,16 @@ public class VcnTest { any(), mGatewayStatusCallbackCaptor.capture()); - // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down + return gatewayConnections; + } + + @Test + public void testGatewayEnteringSafemodeNotifiesVcn() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + final Set<VcnGatewayConnection> gatewayConnections = + startGatewaysAndGetGatewayConnections(requestListener); + + // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down // all Gateways final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); statusCallback.onEnteredSafeMode(); @@ -181,4 +207,31 @@ public class VcnTest { verify(mVcnNetworkProvider).unregisterListener(requestListener); verify(mVcnCallback).onEnteredSafeMode(); } + + @Test + public void testGatewayQuit() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + final Set<VcnGatewayConnection> gatewayConnections = + new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener)); + + final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); + statusCallback.onQuit(); + mTestLooper.dispatchAll(); + + // Verify that the VCN requests the networkRequests be resent + assertEquals(1, mVcn.getVcnGatewayConnections().size()); + verify(mVcnNetworkProvider).resendAllRequests(requestListener); + + // Verify that the VcnGatewayConnection is restarted + triggerVcnRequestListeners(requestListener); + mTestLooper.dispatchAll(); + assertEquals(2, mVcn.getVcnGatewayConnections().size()); + verify(mDeps, times(gatewayConnections.size() + 1)) + .newVcnGatewayConnection( + eq(mVcnContext), + eq(TEST_SUB_GROUP), + eq(mSubscriptionSnapshot), + any(), + mGatewayStatusCallbackCaptor.capture()); + } } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index b760958e0edc..13e090d9d843 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -2263,6 +2263,16 @@ int LinkCommand::Action(const std::vector<std::string>& args) { return 1; } + if (shared_lib_ && options_.private_symbols) { + // If a shared library styleable in a public R.java uses a private attribute, attempting to + // reference the private attribute within the styleable array will cause a link error because + // the private attribute will not be emitted in the public R.java. + context.GetDiagnostics()->Error(DiagMessage() + << "--shared-lib cannot currently be used in combination with" + << " --private-symbols"); + return 1; + } + if (options_.merge_only && !static_lib_) { context.GetDiagnostics()->Error( DiagMessage() << "the --merge-only flag can be only used when building a static library"); diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 062dd8eac975..73072a963d09 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -14,13 +14,16 @@ * limitations under the License. */ -#include "AppInfo.h" #include "Link.h" +#include <android-base/file.h> + +#include "AppInfo.h" #include "LoadedApk.h" #include "test/Test.h" using testing::Eq; +using testing::HasSubstr; using testing::Ne; namespace aapt { @@ -317,4 +320,76 @@ TEST_F(LinkTest, AppInfoWithUsesSplit) { ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag)); } +TEST_F(LinkTest, SharedLibraryAttributeRJava) { + StdErrDiagnostics diag; + const std::string lib_values = + R"(<resources> + <attr name="foo"/> + <public type="attr" name="foo" id="0x00010001"/> + <declare-styleable name="LibraryStyleable"> + <attr name="foo" /> + </declare-styleable> + </resources>)"; + + const std::string client_values = + R"(<resources> + <attr name="bar" /> + <declare-styleable name="ClientStyleable"> + <attr name="com.example.lib:foo" /> + <attr name="bar" /> + </declare-styleable> + </resources>)"; + + // Build a library with a public attribute + const std::string lib_res = GetTestPath("library-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), lib_values, lib_res, &diag)); + + const std::string lib_apk = GetTestPath("library.apk"); + const std::string lib_java = GetTestPath("library_java"); + // clang-format off + auto lib_manifest = ManifestBuilder(this) + .SetPackageName("com.example.lib") + .Build(); + + auto lib_link_args = LinkCommandBuilder(this) + .SetManifestFile(lib_manifest) + .AddFlag("--shared-lib") + .AddParameter("--java", lib_java) + .AddCompiledResDir(lib_res, &diag) + .Build(lib_apk); + // clang-format on + ASSERT_TRUE(Link(lib_link_args, &diag)); + + const std::string lib_r_java = lib_java + "/com/example/lib/R.java"; + std::string lib_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(lib_r_java, &lib_r_contents)); + EXPECT_THAT(lib_r_contents, HasSubstr(" public static int foo=0x00010001;")); + EXPECT_THAT(lib_r_contents, HasSubstr(" com.example.lib.R.attr.foo")); + + // Build a client that uses the library attribute in a declare-styleable + const std::string client_res = GetTestPath("client-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), client_values, client_res, &diag)); + + const std::string client_apk = GetTestPath("client.apk"); + const std::string client_java = GetTestPath("client_java"); + // clang-format off + auto client_manifest = ManifestBuilder(this) + .SetPackageName("com.example.client") + .Build(); + + auto client_link_args = LinkCommandBuilder(this) + .SetManifestFile(client_manifest) + .AddParameter("--java", client_java) + .AddParameter("-I", lib_apk) + .AddCompiledResDir(client_res, &diag) + .Build(client_apk); + // clang-format on + ASSERT_TRUE(Link(client_link_args, &diag)); + + const std::string client_r_java = client_java + "/com/example/client/R.java"; + std::string client_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents)); + EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000")); +} + } // namespace aapt diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index eb0ade62d542..4b90b4f534cc 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -570,7 +570,6 @@ class PackageFlattener { ResourceEntry* entry = sorted_entries->at(entryIndex); // Populate the config masks for this entry. - if (entry->visibility.level == Visibility::Level::kPublic) { config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); } diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 1e4b6816075a..995495ac56a8 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -70,8 +70,8 @@ class PrimitiveMember : public ClassMember { return name_; } - void Print(bool final, text::Printer* printer, bool strip_api_annotations = false) - const override { + void Print(bool final, text::Printer* printer, + bool strip_api_annotations = false) const override { using std::to_string; ClassMember::Print(final, printer, strip_api_annotations); @@ -127,13 +127,13 @@ using IntMember = PrimitiveMember<uint32_t>; using ResourceMember = PrimitiveMember<ResourceId>; using StringMember = PrimitiveMember<std::string>; -template <typename T> +template <typename T, typename StringConverter> class PrimitiveArrayMember : public ClassMember { public: explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {} void AddElement(const T& val) { - elements_.push_back(val); + elements_.emplace_back(val); } bool empty() const override { @@ -158,7 +158,7 @@ class PrimitiveArrayMember : public ClassMember { printer->Println(); } - printer->Print(to_string(*current)); + printer->Print(StringConverter::ToString(*current)); if (std::distance(current, end) > 1) { printer->Print(", "); } @@ -175,7 +175,24 @@ class PrimitiveArrayMember : public ClassMember { std::vector<T> elements_; }; -using ResourceArrayMember = PrimitiveArrayMember<ResourceId>; +struct FieldReference { + explicit FieldReference(std::string reference) : ref(std::move(reference)) { + } + std::string ref; +}; + +struct ResourceArrayMemberStringConverter { + static std::string ToString(const std::variant<ResourceId, FieldReference>& ref) { + if (auto id = std::get_if<ResourceId>(&ref)) { + return to_string(*id); + } else { + return std::get<FieldReference>(ref).ref; + } + } +}; + +using ResourceArrayMember = PrimitiveArrayMember<std::variant<ResourceId, FieldReference>, + ResourceArrayMemberStringConverter>; // Represents a method in a class. class MethodDefinition : public ClassMember { diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index f0f839d968d5..59dd481607e9 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -224,7 +224,16 @@ static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) { return cmp_ids_dynamic_after_framework(lhs_id, rhs_id); } -void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, +static FieldReference GetRFieldReference(const ResourceName& name, + StringPiece fallback_package_name) { + const std::string package_name = + name.package.empty() ? fallback_package_name.to_string() : name.package; + const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry); + return FieldReference( + StringPrintf("%s.R.%s.%s", package_name.c_str(), to_string(name.type).data(), entry.c_str())); +} + +bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, const Styleable& styleable, const StringPiece& package_name_to_generate, ClassDefinition* out_class_def, @@ -340,14 +349,29 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res // Add the ResourceIds to the array member. for (size_t i = 0; i < attr_count; i++) { - const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0)); - array_def->AddElement(id); + const StyleableAttr& attr = sorted_attributes[i]; + std::string r_txt_contents; + if (attr.symbol && attr.symbol.value().is_dynamic) { + if (!attr.attr_ref->name) { + error_ = "unable to determine R.java field name of dynamic resource"; + return false; + } + + const FieldReference field_name = + GetRFieldReference(attr.attr_ref->name.value(), package_name_to_generate); + array_def->AddElement(field_name); + r_txt_contents = field_name.ref; + } else { + const ResourceId attr_id = attr.attr_ref->id.value_or_default(ResourceId(0)); + array_def->AddElement(attr_id); + r_txt_contents = to_string(attr_id); + } if (r_txt_printer != nullptr) { if (i != 0) { r_txt_printer->Print(","); } - r_txt_printer->Print(" ").Print(id.to_string()); + r_txt_printer->Print(" ").Print(r_txt_contents); } } @@ -419,19 +443,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res } } - // If there is a rewrite method to generate, add the statements that rewrite package IDs - // for this styleable. - if (out_rewrite_method != nullptr) { - out_rewrite_method->AppendStatement( - StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data())); - out_rewrite_method->AppendStatement( - StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data())); - out_rewrite_method->AppendStatement( - StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | packageIdBits;", - array_field_name.data(), array_field_name.data())); - out_rewrite_method->AppendStatement(" }"); - out_rewrite_method->AppendStatement("}"); - } + return true; } void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id, @@ -448,8 +460,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso const std::string field_name = TransformToFieldName(name.entry); if (out_class_def != nullptr) { - std::unique_ptr<ResourceMember> resource_member = - util::make_unique<ResourceMember>(field_name, real_id); + auto resource_member = util::make_unique<ResourceMember>(field_name, real_id); // Build the comments and annotations for this entry. AnnotationProcessor* processor = resource_member->GetCommentBuilder(); @@ -551,12 +562,11 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate if (resource_name.type == ResourceType::kStyleable) { CHECK(!entry->values.empty()); - - const Styleable* styleable = - static_cast<const Styleable*>(entry->values.front()->value.get()); - - ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def, - out_rewrite_method_def, r_txt_printer); + const auto styleable = reinterpret_cast<const Styleable*>(entry->values.front()->value.get()); + if (!ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, + out_type_class_def, out_rewrite_method_def, r_txt_printer)) { + return false; + } } else { ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def, r_txt_printer); @@ -626,8 +636,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, if (type->type == ResourceType::kAttr) { // Also include private attributes in this same class. - const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate); - if (priv_type) { + if (const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate)) { if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(), rewrite_method.get(), r_txt_printer.get())) { return false; diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 853120b3cb98..d9d1b39805f9 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -105,7 +105,7 @@ class JavaClassGenerator { // Writes a styleable resource to the R.java file, optionally writing out a rewrite rule for // its package ID if `out_rewrite_method` is not nullptr. // `package_name_to_generate` is the package - void ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, + bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, const Styleable& styleable, const android::StringPiece& package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 04e20101a0dd..ec5b4151b1a6 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -581,7 +581,7 @@ TEST(JavaClassGeneratorTest, SortsDynamicAttributesAfterFrameworkAttributes) { out.Flush(); EXPECT_THAT(output, HasSubstr("public static final int[] MyStyleable={")); - EXPECT_THAT(output, HasSubstr("0x01010000, 0x00010000")); + EXPECT_THAT(output, HasSubstr("0x01010000, lib.R.attr.dynamic_attr")); EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_android_framework_attr=0;")); EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_dynamic_attr=1;")); } diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index daedc2a14767..98ee63d2e5c6 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -370,11 +370,11 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = res_id; - s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package); } if (s) { s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package); return s; } return {}; @@ -417,11 +417,11 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = id; - s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package); } if (s) { s->is_public = (*flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package); return s; } return {}; diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index 5386802dbc8e..f94f0fe1144a 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -18,18 +18,17 @@ #include <dirent.h> -#include "android-base/errors.h" -#include "android-base/file.h" -#include "android-base/stringprintf.h" -#include "android-base/utf8.h" -#include "androidfw/StringPiece.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" +#include <android-base/errors.h> +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android-base/utf8.h> +#include <androidfw/StringPiece.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> #include "cmd/Compile.h" #include "cmd/Link.h" #include "io/FileStream.h" -#include "io/Util.h" #include "util/Files.h" using testing::Eq; @@ -170,4 +169,74 @@ void CommandTestFixture::AssertLoadXml(LoadedApk* apk, const io::IData* data, } } +ManifestBuilder::ManifestBuilder(CommandTestFixture* fixture) : fixture_(fixture) { +} + +ManifestBuilder& ManifestBuilder::SetPackageName(const std::string& package_name) { + package_name_ = package_name; + return *this; +} + +ManifestBuilder& ManifestBuilder::AddContents(const std::string& contents) { + contents_ += contents + "\n"; + return *this; +} + +std::string ManifestBuilder::Build(const std::string& file_path) { + const char* manifest_template = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="%s"> + %s + </manifest>)"; + + fixture_->WriteFile(file_path, android::base::StringPrintf( + manifest_template, package_name_.c_str(), contents_.c_str())); + return file_path; +} + +std::string ManifestBuilder::Build() { + return Build(fixture_->GetTestPath("AndroidManifest.xml")); +} + +LinkCommandBuilder::LinkCommandBuilder(CommandTestFixture* fixture) : fixture_(fixture) { +} + +LinkCommandBuilder& LinkCommandBuilder::SetManifestFile(const std::string& file) { + manifest_supplied_ = true; + args_.emplace_back("--manifest"); + args_.emplace_back(file); + return *this; +} + +LinkCommandBuilder& LinkCommandBuilder::AddFlag(const std::string& flag) { + args_.emplace_back(flag); + return *this; +} + +LinkCommandBuilder& LinkCommandBuilder::AddCompiledResDir(const std::string& dir, + IDiagnostics* diag) { + if (auto files = file::FindFiles(dir, diag)) { + for (std::string& compile_file : files.value()) { + args_.emplace_back(file::BuildPath({dir, compile_file})); + } + } + return *this; +} + +LinkCommandBuilder& LinkCommandBuilder::AddParameter(const std::string& param, + const std::string& value) { + args_.emplace_back(param); + args_.emplace_back(value); + return *this; +} + +std::vector<std::string> LinkCommandBuilder::Build(const std::string& out_apk) { + if (!manifest_supplied_) { + SetManifestFile(ManifestBuilder(fixture_).Build()); + } + args_.emplace_back("-o"); + args_.emplace_back(out_apk); + return args_; +} + } // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h index 457d65e30b65..f8c4889aee3b 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -32,7 +32,7 @@ namespace aapt { class TestDirectoryFixture : public ::testing::Test { public: TestDirectoryFixture() = default; - virtual ~TestDirectoryFixture() = default; + ~TestDirectoryFixture() override = default; // Creates the test directory or clears its contents if it contains previously created files. void SetUp() override; @@ -41,14 +41,14 @@ class TestDirectoryFixture : public ::testing::Test { void TearDown() override; // Retrieve the test directory of the fixture. - const android::StringPiece GetTestDirectory() { + android::StringPiece GetTestDirectory() { return temp_dir_; } // Retrieves the absolute path of the specified relative path in the test directory. Directories // should be separated using forward slashes ('/'), and these slashes will be translated to // backslashes when running Windows tests. - const std::string GetTestPath(const android::StringPiece& path) { + std::string GetTestPath(const android::StringPiece& path) { std::string base = temp_dir_; for (android::StringPiece part : util::Split(path, '/')) { file::AppendPath(&base, part); @@ -68,7 +68,7 @@ class TestDirectoryFixture : public ::testing::Test { class CommandTestFixture : public TestDirectoryFixture { public: CommandTestFixture() = default; - virtual ~CommandTestFixture() = default; + ~CommandTestFixture() override = default; // Wries the contents of the file to the specified path. The file is compiled and the flattened // file is written to the out directory. @@ -99,6 +99,33 @@ class CommandTestFixture : public TestDirectoryFixture { DISALLOW_COPY_AND_ASSIGN(CommandTestFixture); }; +struct ManifestBuilder { + explicit ManifestBuilder(CommandTestFixture* fixture); + ManifestBuilder& AddContents(const std::string& contents); + ManifestBuilder& SetPackageName(const std::string& package_name); + std::string Build(const std::string& file_path); + std::string Build(); + + private: + CommandTestFixture* fixture_; + std::string package_name_ = CommandTestFixture::kDefaultPackageName; + std::string contents_; +}; + +struct LinkCommandBuilder { + explicit LinkCommandBuilder(CommandTestFixture* fixture); + LinkCommandBuilder& AddCompiledResDir(const std::string& dir, IDiagnostics* diag); + LinkCommandBuilder& AddFlag(const std::string& flag); + LinkCommandBuilder& AddParameter(const std::string& param, const std::string& value); + LinkCommandBuilder& SetManifestFile(const std::string& manifest_path); + std::vector<std::string> Build(const std::string& out_apk_path); + + private: + CommandTestFixture* fixture_; + std::vector<std::string> args_; + bool manifest_supplied_ = false; +}; + } // namespace aapt #endif // AAPT_TEST_FIXTURE_H
\ No newline at end of file diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp index df64a801e213..2a88732b50b1 100644 --- a/tools/bit/make.cpp +++ b/tools/bit/make.cpp @@ -89,8 +89,8 @@ BuildVars::BuildVars(const string& outDir, const string& buildProduct, } Json::Value json; - Json::Reader reader; - if (!reader.parse(stream, json)) { + Json::CharReaderBuilder builder; + if (!Json::parseFromStream(builder, stream, &json, /* errorMessage = */ nullptr)) { return; } @@ -132,8 +132,9 @@ BuildVars::save() return; } - Json::StyledStreamWriter writer(" "); - + Json::StreamWriterBuilder factory; + factory["indentation"] = " "; + std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter()); Json::Value json(Json::objectValue); for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) { @@ -141,7 +142,7 @@ BuildVars::save() } std::ofstream stream(m_filename, std::ofstream::binary); - writer.write(stream, json); + writer->write(json, &stream); } string @@ -212,8 +213,8 @@ read_modules(const string& buildOut, const string& device, map<string,Module>* r } Json::Value json; - Json::Reader reader; - if (!reader.parse(stream, json)) { + Json::CharReaderBuilder builder; + if (!Json::parseFromStream(builder, stream, &json, /* errorMessage = */ nullptr)) { json_error(filename, "can't parse json format", quiet); return; } diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index ebc0ec1640f5..e775e1adb04e 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -11,12 +11,6 @@ from fontTools import ttLib EMOJI_VS = 0xFE0F -#TODO(179952916): Rename CutiveMono and DancingScript -CANONICAL_NAME_EXCEPTION_LIST = [ - 'CutiveMono.ttf', - 'DancingScript-Regular.ttf', -] - LANG_TO_SCRIPT = { 'as': 'Beng', 'be': 'Cyrl', @@ -703,8 +697,6 @@ def getSuffix(font): def check_canonical_name(): for record in _all_fonts: file_name, index = record.font - if file_name in CANONICAL_NAME_EXCEPTION_LIST: - continue if index and index != 0: continue |