diff options
672 files changed, 25037 insertions, 8040 deletions
diff --git a/Android.bp b/Android.bp index 554ceae3adad..4e2b156afbbf 100644 --- a/Android.bp +++ b/Android.bp @@ -226,6 +226,7 @@ filegroup { ":framework-wifi-non-updatable-sources", ":PacProcessor-aidl-sources", ":ProxyHandler-aidl-sources", + ":net-utils-framework-common-srcs", // AIDL from frameworks/base/native/ ":platform-compat-native-aidl", @@ -348,7 +349,6 @@ java_library { "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", - "android.hardware.wifi-V1.0-java-constants", "devicepolicyprotosnano", "com.android.sysprop.apex", @@ -416,6 +416,13 @@ filegroup { } filegroup { + name: "graphicsstats_proto", + srcs: [ + "libs/hwui/protos/graphicsstats.proto", + ], +} + +filegroup { name: "libvibrator_aidl", srcs: [ "core/java/android/os/IExternalVibrationController.aidl", @@ -499,7 +506,10 @@ java_library { defaults: ["framework-defaults"], srcs: [":framework-all-sources"], installable: false, - static_libs: ["exoplayer2-core"], + static_libs: [ + "exoplayer2-core", + "android.hardware.wifi-V1.0-java-constants", + ], apex_available: ["//apex_available:platform"], } @@ -597,17 +607,22 @@ gensrcs { filegroup { name: "framework-annotations", srcs: [ + "core/java/android/annotation/CallbackExecutor.java", + "core/java/android/annotation/CheckResult.java", "core/java/android/annotation/IntDef.java", "core/java/android/annotation/IntRange.java", "core/java/android/annotation/NonNull.java", "core/java/android/annotation/Nullable.java", "core/java/android/annotation/RequiresPermission.java", "core/java/android/annotation/SdkConstant.java", + "core/java/android/annotation/StringDef.java", "core/java/android/annotation/SystemApi.java", + "core/java/android/annotation/SystemService.java", "core/java/android/annotation/TestApi.java", "core/java/android/annotation/UnsupportedAppUsage.java", "core/java/com/android/internal/annotations/GuardedBy.java", "core/java/com/android/internal/annotations/VisibleForTesting.java", + "core/java/com/android/internal/annotations/Immutable.java", ], } @@ -622,6 +637,9 @@ filegroup { visibility: ["//frameworks/opt/net/ike"], srcs: [ "core/java/android/net/annotations/PolicyDirection.java", + "core/java/com/android/internal/util/IState.java", + "core/java/com/android/internal/util/State.java", + "core/java/com/android/internal/util/StateMachine.java", "telephony/java/android/telephony/Annotation.java", ], } @@ -1141,13 +1159,36 @@ filegroup { ], } +// utility classes statically linked into framework-wifi and dynamically linked +// into wifi-service +java_library { + name: "framework-wifi-util-lib", + sdk_version: "core_current", + srcs: [ + "core/java/android/content/pm/BaseParceledListSlice.java", + "core/java/android/content/pm/ParceledListSlice.java", + "core/java/android/net/shared/Inet4AddressUtils.java", + "core/java/android/os/HandlerExecutor.java", + "core/java/com/android/internal/util/AsyncChannel.java", + "core/java/com/android/internal/util/AsyncService.java", + "core/java/com/android/internal/util/Protocol.java", + "core/java/com/android/internal/util/Preconditions.java", + "telephony/java/android/telephony/Annotation.java", + ], + libs: [ + "framework-annotations-lib", + "unsupportedappusage", + "android_system_stubs_current", + ], + visibility: ["//frameworks/base/wifi"], +} + +// utility classes statically linked into wifi-service filegroup { name: "framework-wifi-service-shared-srcs", srcs: [ - ":framework-annotations", "core/java/android/net/InterfaceConfiguration.java", "core/java/android/os/BasicShellCommandHandler.java", - "core/java/android/os/HandlerExecutor.java", "core/java/android/util/BackupUtils.java", "core/java/android/util/LocalLog.java", "core/java/android/util/Rational.java", @@ -1155,11 +1196,9 @@ filegroup { "core/java/com/android/internal/util/HexDump.java", "core/java/com/android/internal/util/IState.java", "core/java/com/android/internal/util/MessageUtils.java", - "core/java/com/android/internal/util/Preconditions.java", "core/java/com/android/internal/util/State.java", "core/java/com/android/internal/util/StateMachine.java", "core/java/com/android/internal/util/WakeupMessage.java", - "core/java/com/android/internal/util/XmlUtils.java", ], } diff --git a/StubLibraries.bp b/StubLibraries.bp index d1950474da5a..baa3c615039d 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -41,10 +41,9 @@ packages_to_document = [ ] stubs_defaults { - name: "metalava-api-stubs-default", + name: "metalava-non-updatable-api-stubs-default", srcs: [ ":framework-non-updatable-sources", - ":framework-updatable-sources", "core/java/**/*.logtags", ":opt-telephony-srcs", ":opt-net-voip-srcs", @@ -64,14 +63,23 @@ stubs_defaults { "sdk-dir", "api-versions-jars-dir", ], - sdk_version: "core_platform", filter_packages: packages_to_document, } +stubs_defaults { + name: "metalava-api-stubs-default", + defaults: ["metalava-non-updatable-api-stubs-default"], + srcs: [":framework-updatable-sources"], + sdk_version: "core_platform", +} + ///////////////////////////////////////////////////////////////////// // *-api-stubs-docs modules providing source files for the stub libraries ///////////////////////////////////////////////////////////////////// +// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs +// from the non-updatable part of the platform as well as from the updatable +// modules droidstubs { name: "api-stubs-docs", defaults: ["metalava-api-stubs-default"], @@ -112,7 +120,10 @@ droidstubs { arg_files: [ "core/res/AndroidManifest.xml", ], - args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)", + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)", check_api: { current: { api_file: "api/system-current.txt", @@ -155,6 +166,111 @@ droidstubs { } ///////////////////////////////////////////////////////////////////// +// Following droidstubs modules are for extra APIs for modules. +// The framework currently have two more API surfaces for modules: +// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES) +///////////////////////////////////////////////////////////////////// + +// TODO(b/146727827) remove the *-api modules when we can teach metalava +// about the relationship among the API surfaces. Currently, these modules are only to generate +// the API signature files and ensure that the APIs evolve in a backwards compatible manner. +// They however are NOT used for building the API stub. +droidstubs { + name: "module-app-api", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)", + check_api: { + current: { + api_file: "api/module-app-current.txt", + removed_api_file: "api/module-app-removed.txt", + }, + // TODO(b/147559833) enable the compatibility check against the last release API + // and the API lint + //last_released: { + // api_file: ":last-released-module-app-api", + // removed_api_file: "api/module-app-removed.txt", + // baseline_file: ":module-app-api-incompatibilities-with-last-released" + //}, + //api_lint: { + // enabled: true, + // new_since: ":last-released-module-app-api", + // baseline_file: "api/module-app-lint-baseline.txt", + //}, + }, + //jdiff_enabled: true, +} + +droidstubs { + name: "module-lib-api", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + + "process=android.annotation.SystemApi.Process.ALL\\)", + check_api: { + current: { + api_file: "api/module-lib-current.txt", + removed_api_file: "api/module-lib-removed.txt", + }, + // TODO(b/147559833) enable the compatibility check against the last release API + // and the API lint + //last_released: { + // api_file: ":last-released-module-lib-api", + // removed_api_file: "api/module-lib-removed.txt", + // baseline_file: ":module-lib-api-incompatibilities-with-last-released" + //}, + //api_lint: { + // enabled: true, + // new_since: ":last-released-module-lib-api", + // baseline_file: "api/module-lib-lint-baseline.txt", + //}, + }, + //jdiff_enabled: true, +} + +// The following two droidstubs modules generate source files for the API stub libraries for +// modules. Note that they not only include their own APIs but also other APIs that have +// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just +// the ones with 'client=MODULE_LIBRARIES'. +droidstubs { + name: "module-app-api-stubs-docs", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)" + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)", +} + +droidstubs { + name: "module-lib-api-stubs-docs", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)" + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)" + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + + "process=android.annotation.SystemApi.Process.ALL\\)", +} + +///////////////////////////////////////////////////////////////////// // android_*_stubs_current modules are the stubs libraries compiled // from *-api-stubs-docs ///////////////////////////////////////////////////////////////////// @@ -169,7 +285,6 @@ java_defaults { java_resources: [ ":notices-for-framework-stubs", ], - sdk_version: "core_current", system_modules: "none", java_version: "1.8", compile_dex: true, @@ -187,6 +302,7 @@ java_library_static { "private-stub-annotations-jar", ], defaults: ["framework-stubs-default"], + sdk_version: "core_current", } java_library_static { @@ -201,6 +317,7 @@ java_library_static { "private-stub-annotations-jar", ], defaults: ["framework-stubs-default"], + sdk_version: "core_current", } java_library_static { @@ -215,6 +332,37 @@ java_library_static { "private-stub-annotations-jar", ], defaults: ["framework-stubs-default"], + sdk_version: "core_current", +} + +java_library_static { + name: "framework_module_app_stubs_current", + srcs: [ + ":module-app-api-stubs-docs", + ], + libs: [ + "stub-annotations", + "framework-all", + ], + static_libs: [ + "private-stub-annotations-jar", + ], + defaults: ["framework-stubs-default"], +} + +java_library_static { + name: "framework_module_lib_stubs_current", + srcs: [ + ":module-lib-api-stubs-docs", + ], + libs: [ + "stub-annotations", + "framework-all", + ], + static_libs: [ + "private-stub-annotations-jar", + ], + defaults: ["framework-stubs-default"], } ///////////////////////////////////////////////////////////////////// diff --git a/apex/Android.bp b/apex/Android.bp index 56f7db2c8dad..abebfa39fada 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -37,6 +37,36 @@ stubs_defaults { stubs_defaults { name: "framework-module-stubs-defaults-systemapi", - args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ", + args: mainline_stubs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) ", + installable: false, +} + +stubs_defaults { + name: "framework-module-stubs-defaults-module_apps_api", + args: mainline_stubs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) ", + installable: false, +} + +stubs_defaults { + name: "framework-module-stubs-defaults-module_libs_api", + args: mainline_stubs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + + "process=android.annotation.SystemApi.Process.ALL\\) ", installable: false, } diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp index 4ebafce84886..8aed5d04a32b 100644 --- a/apex/appsearch/service/Android.bp +++ b/apex/appsearch/service/Android.bp @@ -23,5 +23,6 @@ java_library { ], static_libs: [ "icing-java-proto-lite", - ] + ], + apex_available: [ "com.android.appsearch" ], } diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 3f58c72cbdc2..f8b2f32e1a2f 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -761,6 +761,7 @@ public class DeviceIdleController extends SystemService @Override public void onTrigger(TriggerEvent event) { synchronized (DeviceIdleController.this) { + // One_shot sensors (which call onTrigger) are unregistered when onTrigger is called active = false; motionLocked(); } @@ -769,6 +770,9 @@ public class DeviceIdleController extends SystemService @Override public void onSensorChanged(SensorEvent event) { synchronized (DeviceIdleController.this) { + // Since one_shot sensors are unregistered when onTrigger is called, unregister + // listeners here so that the MotionListener is in a consistent state when it calls + // out to motionLocked. mSensorManager.unregisterListener(this, mMotionSensor); active = false; motionLocked(); diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING new file mode 100644 index 000000000000..8fbfb1daaf6f --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING @@ -0,0 +1,22 @@ +{ + "presubmit": [ + { + "name": "FrameworksMockingServicesTests", + "file_patterns": [ + "DeviceIdleController\\.java" + ], + "options": [ + {"include-filter": "com.android.server.DeviceIdleControllerTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"} + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + {"include-filter": "com.android.server"} + ] + } + ] +}
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING new file mode 100644 index 000000000000..bc7a7d3bef7d --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + {"include-filter": "com.android.server.DeviceIdleControllerTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"} + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + {"include-filter": "com.android.server"} + ] + } + ] +}
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 82292cfeea09..b9df30aa4d95 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -17,7 +17,7 @@ package com.android.server.usage; import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; @@ -441,7 +441,7 @@ public class AppIdleHistory { elapsedRealtime, true); if (idle) { appUsageHistory.currentBucket = STANDBY_BUCKET_RARE; - appUsageHistory.bucketingReason = REASON_MAIN_FORCED; + appUsageHistory.bucketingReason = REASON_MAIN_FORCED_BY_USER; } else { appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE; // This is to pretend that the app was just used, don't freeze the state anymore. diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 58eb58961ac4..eb0b54b1d9fc 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -17,7 +17,8 @@ package com.android.server.usage; import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; @@ -565,7 +566,7 @@ public class AppStandbyController implements AppStandbyInternal { // If the bucket was forced by the user/developer, leave it alone. // A usage event will be the only way to bring it out of this forced state - if (oldMainReason == REASON_MAIN_FORCED) { + if (oldMainReason == REASON_MAIN_FORCED_BY_USER) { return; } final int oldBucket = app.currentBucket; @@ -783,7 +784,7 @@ public class AppStandbyController implements AppStandbyInternal { // Inform listeners if necessary if (previouslyIdle != stillIdle) { maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket, - REASON_MAIN_FORCED, false); + REASON_MAIN_FORCED_BY_USER, false); if (!stillIdle) { notifyBatteryStats(packageName, userId, idle); } @@ -1030,8 +1031,17 @@ public class AppStandbyController implements AppStandbyInternal { callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null); final boolean shellCaller = callingUid == Process.ROOT_UID || callingUid == Process.SHELL_UID; - final boolean systemCaller = UserHandle.isCore(callingUid); - final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED; + final int reason; + // The Settings app runs in the system UID but in a separate process. Assume + // things coming from other processes are due to the user. + if ((UserHandle.isSameApp(callingUid, Process.SYSTEM_UID) && callingPid != Process.myPid()) + || shellCaller) { + reason = REASON_MAIN_FORCED_BY_USER; + } else if (UserHandle.isCore(callingUid)) { + reason = REASON_MAIN_FORCED_BY_SYSTEM; + } else { + reason = REASON_MAIN_PREDICTED; + } final int packageFlags = PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE; @@ -1087,7 +1097,11 @@ public class AppStandbyController implements AppStandbyInternal { } // If the bucket was forced, don't allow prediction to override - if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED && predicted) return; + if (predicted + && ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER + || (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM)) { + return; + } // If the bucket is required to stay in a higher state for a specified duration, don't // override unless the duration has passed diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING new file mode 100644 index 000000000000..cf70878b8899 --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + {"include-filter": "com.android.server.usage"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"} + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + {"include-filter": "com.android.server.usage"} + ] + } + ] +}
\ No newline at end of file diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl index 21b7767e932d..99b9d398e30c 100644 --- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl +++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl @@ -67,11 +67,4 @@ interface IStatsCompanionService { /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); - - /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */ - oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, - in int[] additiveFields, IPullAtomCallback pullerCallback); - - /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */ - oneway void unregisterPullAtomCallback(int atomTag); } diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl index dec56345ec2f..4a259f50d2f6 100644 --- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl +++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl @@ -17,6 +17,7 @@ package android.os; import android.app.PendingIntent; +import android.os.IPullAtomCallback; /** * Binder interface to communicate with the Java-based statistics service helper. @@ -125,4 +126,11 @@ interface IStatsManagerService { * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. */ void removeConfiguration(in long configId, in String packageName); -}
\ No newline at end of file + + /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */ + oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + in int[] additiveFields, IPullAtomCallback pullerCallback); + + /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */ + oneway void unregisterPullAtomCallback(int atomTag); +} diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp index f3a8989e633a..910384845810 100644 --- a/apex/statsd/service/Android.bp +++ b/apex/statsd/service/Android.bp @@ -13,4 +13,8 @@ java_library { "framework-minus-apex", "services.core", ], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index d57afeeb7157..bbb87ab6ce7c 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -75,7 +75,6 @@ import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.IPullAtomCallback; import android.os.IStatsCompanionService; import android.os.IStatsd; import android.os.IStoraged; @@ -263,71 +262,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private StatsManagerService mStatsManagerService; - private static final class PullerKey { - private final int mUid; - private final int mAtomTag; - - PullerKey(int uid, int atom) { - mUid = uid; - mAtomTag = atom; - } - - public int getUid() { - return mUid; - } - - public int getAtom() { - return mAtomTag; - } - - @Override - public int hashCode() { - return Objects.hash(mUid, mAtomTag); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof PullerKey) { - PullerKey other = (PullerKey) obj; - return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); - } - return false; - } - } - - private static final class PullerValue { - private final long mCoolDownNs; - private final long mTimeoutNs; - private int[] mAdditiveFields; - private IPullAtomCallback mCallback; - - PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields, - IPullAtomCallback callback) { - mCoolDownNs = coolDownNs; - mTimeoutNs = timeoutNs; - mAdditiveFields = additiveFields; - mCallback = callback; - } - - public long getCoolDownNs() { - return mCoolDownNs; - } - - public long getTimeoutNs() { - return mTimeoutNs; - } - - public int[] getAdditiveFields() { - return mAdditiveFields; - } - - public IPullAtomCallback getCallback() { - return mCallback; - } - } - - private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>(); - private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); private WifiManager mWifiManager = null; @@ -780,60 +714,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private void addNetworkStats( - int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) { - int size = stats.size(); - long elapsedNanos = SystemClock.elapsedRealtimeNanos(); - long wallClockNanos = SystemClock.currentTimeMicro() * 1000L; - NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling - for (int j = 0; j < size; j++) { - stats.getValues(j, entry); - StatsLogEventWrapper e = new StatsLogEventWrapper(tag, elapsedNanos, wallClockNanos); - e.writeInt(entry.uid); - if (withFGBG) { - e.writeInt(entry.set); - } - e.writeLong(entry.rxBytes); - e.writeLong(entry.rxPackets); - e.writeLong(entry.txBytes); - e.writeLong(entry.txPackets); - ret.add(e); - } - } - - /** - * Allows rollups per UID but keeping the set (foreground/background) slicing. - * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java - */ - private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) { - final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1); - - final NetworkStats.Entry entry = new NetworkStats.Entry(); - entry.iface = NetworkStats.IFACE_ALL; - entry.tag = NetworkStats.TAG_NONE; - entry.metered = NetworkStats.METERED_ALL; - entry.roaming = NetworkStats.ROAMING_ALL; - - int size = stats.size(); - NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values - for (int i = 0; i < size; i++) { - stats.getValues(i, recycle); - - // Skip specific tags, since already counted in TAG_NONE - if (recycle.tag != NetworkStats.TAG_NONE) continue; - - entry.set = recycle.set; // Allows slicing by background/foreground - entry.uid = recycle.uid; - entry.rxBytes = recycle.rxBytes; - entry.rxPackets = recycle.rxPackets; - entry.txBytes = recycle.txBytes; - entry.txPackets = recycle.txPackets; - // Operations purposefully omitted since we don't use them for statsd. - ret.combineValues(entry); - } - return ret; - } - /** * Helper method to extract the Parcelable controller info from a * SynchronousResultReceiver. @@ -881,119 +761,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private void pullWifiBytesTransfer( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - try { - // TODO: Consider caching the following call to get BatteryStatsInternal. - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getWifiIfaces(); - if (ifaces.length == 0) { - return; - } - if (mNetworkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return; - } - // Combine all the metrics per Uid into one record. - NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid(); - addNetworkStats(tagId, pulledData, stats, false); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for wifi bytes has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void pullWifiBytesTransferByFgBg( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - try { - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getWifiIfaces(); - if (ifaces.length == 0) { - return; - } - if (mNetworkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return; - } - NetworkStats stats = rollupNetworkStatsByFGBG( - mNetworkStatsService.getDetailedUidStats(ifaces)); - addNetworkStats(tagId, pulledData, stats, true); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void pullMobileBytesTransfer( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - try { - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getMobileIfaces(); - if (ifaces.length == 0) { - return; - } - if (mNetworkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return; - } - // Combine all the metrics per Uid into one record. - NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid(); - addNetworkStats(tagId, pulledData, stats, false); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for mobile bytes has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void pullBluetoothBytesTransfer( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - BluetoothActivityEnergyInfo info = fetchBluetoothData(); - if (info.getUidTraffic() != null) { - for (UidTraffic traffic : info.getUidTraffic()) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - e.writeInt(traffic.getUid()); - e.writeLong(traffic.getRxBytes()); - e.writeLong(traffic.getTxBytes()); - pulledData.add(e); - } - } - } - - private void pullMobileBytesTransferByFgBg( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - try { - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getMobileIfaces(); - if (ifaces.length == 0) { - return; - } - if (mNetworkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return; - } - NetworkStats stats = rollupNetworkStatsByFGBG( - mNetworkStatsService.getDetailedUidStats(ifaces)); - addNetworkStats(tagId, pulledData, stats, true); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - private void pullCpuTimePerFreq( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { @@ -1143,33 +910,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private void pullBluetoothActivityInfo( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - BluetoothActivityEnergyInfo info = fetchBluetoothData(); - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeLong(info.getTimeStamp()); - e.writeInt(info.getBluetoothStackState()); - e.writeLong(info.getControllerTxTimeMillis()); - e.writeLong(info.getControllerRxTimeMillis()); - e.writeLong(info.getControllerIdleTimeMillis()); - e.writeLong(info.getControllerEnergyUsed()); - pulledData.add(e); - } - - private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() { - final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver( - "bluetooth"); - adapter.requestControllerActivityEnergyInfo(bluetoothReceiver); - return awaitControllerInfo(bluetoothReceiver); - } else { - Slog.e(TAG, "Failed to get bluetooth adapter!"); - return null; - } - } - private void pullSystemElapsedRealtime( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { @@ -1762,21 +1502,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private void pullPowerProfile( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - PowerProfile powerProfile = new PowerProfile(mContext); - Objects.requireNonNull(powerProfile); - - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - ProtoOutputStream proto = new ProtoOutputStream(); - powerProfile.dumpDebug(proto); - proto.flush(); - e.writeStorage(proto.getBytes()); - pulledData.add(e); - } - private void pullBuildInformation(int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); @@ -2382,149 +2107,150 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { long elapsedNanos = SystemClock.elapsedRealtimeNanos(); long wallClockNanos = SystemClock.currentTimeMicro() * 1000L; switch (tagId) { - case StatsLog.WIFI_BYTES_TRANSFER: { - pullWifiBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.MOBILE_BYTES_TRANSFER: { - pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: { - pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: { - pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.BLUETOOTH_BYTES_TRANSFER: { - pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret); - break; - } + case StatsLog.KERNEL_WAKELOCK: { pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.CPU_TIME_PER_FREQ: { pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.CPU_TIME_PER_UID: { pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.CPU_TIME_PER_UID_FREQ: { pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.CPU_CLUSTER_TIME: { pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.CPU_ACTIVE_TIME: { pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.WIFI_ACTIVITY_INFO: { pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.MODEM_ACTIVITY_INFO: { pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret); break; } - case StatsLog.BLUETOOTH_ACTIVITY_INFO: { - pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret); - break; - } + case StatsLog.SYSTEM_UPTIME: { pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.SYSTEM_ELAPSED_REALTIME: { pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROCESS_MEMORY_STATE: { pullProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: { pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROCESS_MEMORY_SNAPSHOT: { pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.SYSTEM_ION_HEAP_SIZE: { pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: { pullProcessSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.BINDER_CALLS: { pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.BINDER_CALLS_EXCEPTIONS: { pullBinderCallsStatsExceptions(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.LOOPER_STATS: { pullLooperStats(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DISK_STATS: { pullDiskStats(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DIRECTORY_USAGE: { pullDirectoryUsage(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.APP_SIZE: { pullAppSize(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.CATEGORY_SIZE: { pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.NUM_FINGERPRINTS_ENROLLED: { pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FINGERPRINT, tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.NUM_FACES_ENROLLED: { pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FACE, tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROC_STATS: { pullProcessStats(ProcessStats.REPORT_ALL, tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROC_STATS_PKG_PROC: { pullProcessStats(ProcessStats.REPORT_PKG_PROC_STATS, tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DISK_IO: { pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret); break; } - case StatsLog.POWER_PROFILE: { - pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret); - break; - } + case StatsLog.BUILD_INFORMATION: { pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROCESS_CPU_TIME: { pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret); break; @@ -2533,73 +2259,90 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DEVICE_CALCULATED_POWER_USE: { pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: { pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: { pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.TEMPERATURE: { pullTemperature(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.COOLING_DEVICE: { pullCoolingDevices(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DEBUG_ELAPSED_CLOCK: { pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: { pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.ROLE_HOLDER: { pullRoleHolders(elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DANGEROUS_PERMISSION_STATE: { pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: { pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.TIME_ZONE_DATA_INFO: { pullTimeZoneDataInfo(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.EXTERNAL_STORAGE_INFO: { pullExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO: { pullAppsOnExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.FACE_SETTINGS: { pullFaceSettings(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.APP_OPS: { pullAppOps(elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.NOTIFICATION_REMOTE_VIEWS: { pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS, tagId, elapsedNanos, wallClockNanos, ret); break; } + default: Slog.w(TAG, "No such tagId data as " + tagId); return null; @@ -2634,57 +2377,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - @Override - public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, - int[] additiveFields, IPullAtomCallback pullerCallback) { - synchronized (sStatsdLock) { - // Always cache the puller in SCS. - // If statsd is down, we will register it when it comes back up. - int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - PullerKey key = new PullerKey(callingUid, atomTag); - PullerValue val = new PullerValue( - coolDownNs, timeoutNs, additiveFields, pullerCallback); - mPullers.put(key, val); - - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag); - return; - } - try { - sStatsd.registerPullAtomCallback( - callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - - @Override - public void unregisterPullAtomCallback(int atomTag) { - synchronized (sStatsdLock) { - // Always remove the puller in SCS. - // If statsd is down, we will not register it when it comes back up. - int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - PullerKey key = new PullerKey(callingUid, atomTag); - mPullers.remove(key); - - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag); - return; - } - try { - sStatsd.unregisterPullAtomCallback(callingUid, atomTag); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } // Statsd related code @@ -2763,8 +2455,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Pull the latest state of UID->app name, version mapping when // statsd starts. informAllUidsLocked(mContext); - // Register all pullers. If SCS has just started, this should be empty. - registerAllPullersLocked(); } finally { restoreCallingIdentity(token); } @@ -2776,17 +2466,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - @GuardedBy("sStatsdLock") - private void registerAllPullersLocked() throws RemoteException { - // TODO: pass in one call, using a file descriptor (similar to uidmap). - for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) { - PullerKey key = entry.getKey(); - PullerValue val = entry.getValue(); - sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(), - val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback()); - } - } - private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index b27d0f7699fc..04d8b006f51d 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -24,6 +24,7 @@ import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; import android.os.Binder; +import android.os.IPullAtomCallback; import android.os.IStatsManagerService; import android.os.IStatsd; import android.os.Process; @@ -60,8 +61,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { @GuardedBy("mLock") private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>(); @GuardedBy("mLock") - private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = - new ArrayMap<>(); + private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>(); @GuardedBy("mLock") private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap = new ArrayMap<>(); @@ -72,8 +72,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { } private static class ConfigKey { - private int mUid; - private long mConfigId; + private final int mUid; + private final long mConfigId; ConfigKey(int uid, long configId) { mUid = uid; @@ -103,6 +103,126 @@ public class StatsManagerService extends IStatsManagerService.Stub { } } + private static class PullerKey { + private final int mUid; + private final int mAtomTag; + + PullerKey(int uid, int atom) { + mUid = uid; + mAtomTag = atom; + } + + public int getUid() { + return mUid; + } + + public int getAtom() { + return mAtomTag; + } + + @Override + public int hashCode() { + return Objects.hash(mUid, mAtomTag); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PullerKey) { + PullerKey other = (PullerKey) obj; + return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); + } + return false; + } + } + + private static class PullerValue { + private final long mCoolDownNs; + private final long mTimeoutNs; + private final int[] mAdditiveFields; + private final IPullAtomCallback mCallback; + + PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields, + IPullAtomCallback callback) { + mCoolDownNs = coolDownNs; + mTimeoutNs = timeoutNs; + mAdditiveFields = additiveFields; + mCallback = callback; + } + + public long getCoolDownNs() { + return mCoolDownNs; + } + + public long getTimeoutNs() { + return mTimeoutNs; + } + + public int[] getAdditiveFields() { + return mAdditiveFields; + } + + public IPullAtomCallback getCallback() { + return mCallback; + } + } + + private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>(); + + @Override + public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + int[] additiveFields, IPullAtomCallback pullerCallback) { + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + PullerKey key = new PullerKey(callingUid, atomTag); + PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback); + + // Always cache the puller in StatsManagerService. If statsd is down, we will register the + // puller when statsd comes back up. + synchronized (mLock) { + mPullers.put(key, val); + } + + IStatsd statsd = getStatsdNonblocking(); + if (statsd == null) { + return; + } + + try { + statsd.registerPullAtomCallback( + callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void unregisterPullAtomCallback(int atomTag) { + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + PullerKey key = new PullerKey(callingUid, atomTag); + + // Always remove the puller from StatsManagerService even if statsd is down. When statsd + // comes back up, we will not re-register the removed puller. + synchronized (mLock) { + mPullers.remove(key); + } + + IStatsd statsd = getStatsdNonblocking(); + if (statsd == null) { + return; + } + + try { + statsd.unregisterPullAtomCallback(callingUid, atomTag); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public void setDataFetchOperation(long configId, PendingIntent pendingIntent, String packageName) { @@ -441,46 +561,85 @@ public class StatsManagerService extends IStatsManagerService.Stub { if (statsd == null) { return; } - // Since we do not want to make an IPC with the a lock held, we first create local deep - // copies of the data with the lock held before iterating through the maps. + + final long token = Binder.clearCallingIdentity(); + try { + registerAllPullers(statsd); + registerAllDataFetchOperations(statsd); + registerAllActiveConfigsChangedOperations(statsd); + registerAllBroadcastSubscribers(statsd); + } catch (RemoteException e) { + Slog.e(TAG, "StatsManager failed to (re-)register data with statsd"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllPullers(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. + ArrayMap<PullerKey, PullerValue> pullersCopy; + synchronized (mLock) { + pullersCopy = new ArrayMap<>(mPullers); + } + + for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) { + PullerKey key = entry.getKey(); + PullerValue value = entry.getValue(); + statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(), + value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback()); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy; - ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; - ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy; synchronized (mLock) { dataFetchCopy = new ArrayMap<>(mDataFetchPirMap); - activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); - broadcastSubscriberCopy = new ArrayMap<>(); - for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry - : mBroadcastSubscriberPirMap.entrySet()) { - broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue())); - } } + for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) { ConfigKey key = entry.getKey(); - try { - statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setDataFetchOperation from pirMap"); - } + statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. + ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; + synchronized (mLock) { + activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); + } + + for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) { + statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); } - for (Map.Entry<Integer, PendingIntentRef> entry - : activeConfigsChangedCopy.entrySet()) { - try { - statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap"); + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a deep copy of + // the data with the lock held before iterating through the map. + ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy = + new ArrayMap<>(); + synchronized (mLock) { + for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : + mBroadcastSubscriberPirMap.entrySet()) { + broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue())); } } - for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry - : broadcastSubscriberCopy.entrySet()) { + + for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : + mBroadcastSubscriberPirMap.entrySet()) { + ConfigKey configKey = entry.getKey(); for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) { - ConfigKey configKey = entry.getKey(); - try { - statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), - subscriberEntry.getValue(), configKey.getUid()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap"); - } + statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), + subscriberEntry.getValue(), configKey.getUid()); } } } diff --git a/api/current.txt b/api/current.txt index edb31dfdf444..afbb427ed288 100644 --- a/api/current.txt +++ b/api/current.txt @@ -115,6 +115,7 @@ package android { field public static final String READ_LOGS = "android.permission.READ_LOGS"; field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS"; field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; + field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE"; field public static final String READ_SMS = "android.permission.READ_SMS"; field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS"; field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS"; @@ -1144,6 +1145,7 @@ package android { field public static final int resizeable = 16843405; // 0x101028d field public static final int resizeableActivity = 16844022; // 0x10104f6 field public static final int resource = 16842789; // 0x1010025 + field public static final int resourcesMap = 16844297; // 0x1010609 field public static final int restoreAnyVersion = 16843450; // 0x10102ba field @Deprecated public static final int restoreNeedsApplication = 16843421; // 0x101029d field public static final int restrictedAccountType = 16843733; // 0x10103d5 @@ -2923,6 +2925,7 @@ package android.accessibilityservice { method public int getShowMode(); method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener); method public boolean setShowMode(int); + method public boolean switchToInputMethod(@NonNull String); } public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener { @@ -5837,6 +5840,7 @@ package android.app { method public void enableLights(boolean); method public void enableVibration(boolean); method public android.media.AudioAttributes getAudioAttributes(); + method @Nullable public String getConversationId(); method public String getDescription(); method public String getGroup(); method public String getId(); @@ -5844,12 +5848,14 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public CharSequence getName(); + method @Nullable public String getParentChannelId(); method public android.net.Uri getSound(); method public long[] getVibrationPattern(); method public boolean hasUserSetImportance(); method public boolean hasUserSetSound(); method public void setAllowBubbles(boolean); method public void setBypassDnd(boolean); + method public void setConversationId(@Nullable String, @Nullable String); method public void setDescription(String); method public void setGroup(String); method public void setImportance(int); @@ -5862,6 +5868,7 @@ package android.app { method public boolean shouldShowLights(); method public boolean shouldVibrate(); method public void writeToParcel(android.os.Parcel, int); + field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s"; field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR; field public static final String DEFAULT_CHANNEL_ID = "miscellaneous"; } @@ -5903,6 +5910,7 @@ package android.app { method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(String); + method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String); method public android.app.NotificationChannelGroup getNotificationChannelGroup(String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); @@ -6756,6 +6764,7 @@ package android.app.admin { method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); method public CharSequence getDeviceOwnerLockScreenInfo(); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); + method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName); method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName); method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName); @@ -6874,6 +6883,7 @@ package android.app.admin { method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>); method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence); method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); + method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy); method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName); method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String); method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String); @@ -7108,6 +7118,21 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR; } + public final class FactoryResetProtectionPolicy implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts(); + method public boolean isFactoryResetProtectionDisabled(); + method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR; + } + + public static class FactoryResetProtectionPolicy.Builder { + ctor public FactoryResetProtectionPolicy.Builder(); + method @NonNull public android.app.admin.FactoryResetProtectionPolicy build(); + method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean); + } + public class FreezePeriod { ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay); method public java.time.MonthDay getEnd(); @@ -9823,6 +9848,7 @@ package android.content { method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int); method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); + method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle); method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String); method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int); method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String); @@ -11358,6 +11384,7 @@ package android.content.pm { public class CrossProfileApps { method public boolean canInteractAcrossProfiles(); method public boolean canRequestInteractAcrossProfiles(); + method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent(); method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle); method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle); method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles(); @@ -17053,13 +17080,13 @@ package android.hardware.camera2 { method public abstract void close(); method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; method @NonNull public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException; - method public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; - method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; - method public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException; - method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; - method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException; method @NonNull public abstract String getId(); method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; @@ -23655,6 +23682,7 @@ package android.media { field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1 field public static final int TYPE_BUILTIN_MIC = 15; // 0xf field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2 + field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18 field public static final int TYPE_BUS = 21; // 0x15 field public static final int TYPE_DOCK = 13; // 0xd field public static final int TYPE_FM = 14; // 0xe @@ -26288,6 +26316,59 @@ package android.media { field public static final int SURFACE = 2; // 0x2 } + public final class MediaRoute2Info implements android.os.Parcelable { + method public int describeContents(); + method public int getConnectionState(); + method @Nullable public CharSequence getDescription(); + method public int getDeviceType(); + method @Nullable public android.os.Bundle getExtras(); + method @NonNull public java.util.List<java.lang.String> getFeatures(); + method @Nullable public android.net.Uri getIconUri(); + method @NonNull public String getId(); + method @NonNull public CharSequence getName(); + method public int getVolume(); + method public int getVolumeHandling(); + method public int getVolumeMax(); + method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2 + field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1 + field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaRoute2Info> CREATOR; + field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3 + field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2 + field public static final int DEVICE_TYPE_REMOTE_TV = 1; // 0x1 + field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0 + field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 + field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1 + } + + public static final class MediaRoute2Info.Builder { + ctor public MediaRoute2Info.Builder(@NonNull String, @NonNull CharSequence); + ctor public MediaRoute2Info.Builder(@NonNull android.media.MediaRoute2Info); + method @NonNull public android.media.MediaRoute2Info.Builder addFeature(@NonNull String); + method @NonNull public android.media.MediaRoute2Info.Builder addFeatures(@NonNull java.util.Collection<java.lang.String>); + method @NonNull public android.media.MediaRoute2Info build(); + method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures(); + method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String); + method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int); + method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence); + method @NonNull public android.media.MediaRoute2Info.Builder setDeviceType(int); + method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle); + method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri); + method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int); + method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int); + method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int); + } + + public abstract class MediaRoute2ProviderService extends android.app.Service { + ctor public MediaRoute2ProviderService(); + method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>); + method @NonNull public android.os.IBinder onBind(@NonNull android.content.Intent); + method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference); + field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; + } + public class MediaRouter { method public void addCallback(int, android.media.MediaRouter.Callback); method public void addCallback(int, android.media.MediaRouter.Callback, int); @@ -26411,6 +26492,20 @@ package android.media { method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int); } + public class MediaRouter2 { + method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context); + method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes(); + method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference); + method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback); + } + + public static class MediaRouter2.RouteCallback { + ctor public MediaRouter2.RouteCallback(); + method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>); + method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>); + method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>); + } + public class MediaScannerConnection implements android.content.ServiceConnection { ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient); method public void connect(); @@ -26769,6 +26864,22 @@ package android.media { field public static final int URI_COLUMN_INDEX = 2; // 0x2 } + public final class RouteDiscoveryPreference implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getPreferredFeatures(); + method public boolean isActiveScan(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR; + } + + public static final class RouteDiscoveryPreference.Builder { + ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean); + ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference); + method @NonNull public android.media.RouteDiscoveryPreference build(); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>); + } + public final class Session2Command implements android.os.Parcelable { ctor public Session2Command(int); ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle); @@ -28590,7 +28701,9 @@ package android.media.tv { ctor public TvInputService(); method public final android.os.IBinder onBind(android.content.Intent); method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(String); + method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String); method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(String); + method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String); field public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService"; field public static final String SERVICE_META_DATA = "android.media.tv.input"; } @@ -28693,7 +28806,7 @@ package android.media.tv { method public boolean isEncrypted(); method public boolean isHardOfHearing(); method public boolean isSpokenSubtitle(); - method public void writeToParcel(android.os.Parcel, int); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR; field public static final int TYPE_AUDIO = 0; // 0x0 field public static final int TYPE_SUBTITLE = 2; // 0x2 @@ -28702,21 +28815,21 @@ package android.media.tv { public static final class TvTrackInfo.Builder { ctor public TvTrackInfo.Builder(int, @NonNull String); - method public android.media.tv.TvTrackInfo build(); - method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int); + method @NonNull public android.media.tv.TvTrackInfo build(); + method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int); method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean); - method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int); - method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence); + method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int); + method @NonNull public android.media.tv.TvTrackInfo.Builder setDescription(@NonNull CharSequence); method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean); - method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); + method @NonNull public android.media.tv.TvTrackInfo.Builder setExtra(@NonNull android.os.Bundle); method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean); - method public android.media.tv.TvTrackInfo.Builder setLanguage(String); + method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String); method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean); - method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); - method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); - method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int); - method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); - method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoHeight(int); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoWidth(int); } public class TvView extends android.view.ViewGroup { @@ -30394,6 +30507,7 @@ package android.net.wifi { method @Nullable public java.security.cert.X509Certificate[] getCaCertificates(); method public java.security.cert.X509Certificate getClientCertificate(); method @Nullable public java.security.cert.X509Certificate[] getClientCertificateChain(); + method @Nullable public java.security.PrivateKey getClientPrivateKey(); method public String getDomainSuffixMatch(); method public int getEapMethod(); method public String getIdentity(); @@ -30533,6 +30647,7 @@ package android.net.wifi { field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE"; field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION"; + field public static final String ACTION_WIFI_SCAN_AVAILABLE = "android.net.wifi.action.WIFI_SCAN_AVAILABLE"; field @Deprecated public static final int ERROR_AUTHENTICATING = 1; // 0x1 field @Deprecated public static final String EXTRA_BSSID = "bssid"; field public static final String EXTRA_NETWORK_INFO = "networkInfo"; @@ -30541,6 +30656,7 @@ package android.net.wifi { field @Deprecated public static final String EXTRA_NEW_STATE = "newState"; field public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state"; field public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated"; + field public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE"; field @Deprecated public static final String EXTRA_SUPPLICANT_CONNECTED = "connected"; field @Deprecated public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError"; field @Deprecated public static final String EXTRA_WIFI_INFO = "wifiInfo"; @@ -30657,11 +30773,11 @@ package android.net.wifi { ctor public WifiNetworkSuggestion.Builder(); method @NonNull public android.net.wifi.WifiNetworkSuggestion build(); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setBssid(@NonNull android.net.MacAddress); + method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCredentialSharedWithUser(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean); - method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserAllowedToManuallyConnect(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int); @@ -36066,6 +36182,7 @@ package android.os { method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); method public boolean hasUserRestriction(String); method public boolean isDemoUser(); + method public boolean isManagedProfile(); method public boolean isQuietModeEnabled(android.os.UserHandle); method public boolean isSystemUser(); method public boolean isUserAGoat(); @@ -39089,6 +39206,7 @@ package android.provider { method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>); method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri); method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context); + method public static long getGeneration(@NonNull android.content.Context, @NonNull String); method public static android.net.Uri getMediaScannerUri(); method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri); method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context); @@ -39334,6 +39452,7 @@ package android.provider { field @Deprecated public static final String LONGITUDE = "longitude"; field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; field @Deprecated public static final String PICASA_ID = "picasa_id"; + field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type"; } public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns { @@ -39398,6 +39517,7 @@ package android.provider { field public static final String DISPLAY_NAME = "_display_name"; field public static final String DOCUMENT_ID = "document_id"; field public static final String DURATION = "duration"; + field public static final String GENERATION = "generation"; field public static final String GENRE = "genre"; field public static final String HEIGHT = "height"; field public static final String INSTANCE_ID = "instance_id"; @@ -42675,11 +42795,20 @@ package android.service.voice { method public android.content.Intent createEnrollIntent(); method public android.content.Intent createReEnrollIntent(); method public android.content.Intent createUnEnrollIntent(); + method public int getParameter(int); + method public int getSupportedAudioCapabilities(); method public int getSupportedRecognitionModes(); + method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); + method public int setParameter(int, int); method public boolean startRecognition(int); method public boolean stopRecognition(); + field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 + field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe @@ -42702,6 +42831,11 @@ package android.service.voice { method @Nullable public byte[] getTriggerAudio(); } + public static final class AlwaysOnHotwordDetector.ModelParamRange { + method public int end(); + method public int start(); + } + public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); @@ -45033,7 +45167,7 @@ package android.telephony { field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool"; field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool"; field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool"; - field public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; + field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool"; field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool"; field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool"; @@ -45056,6 +45190,7 @@ package android.telephony { field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int"; field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; + field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; @@ -45524,7 +45659,7 @@ package android.telephony { method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback); } - public final class MmsManager { + public class MmsManager { method public void downloadMultimediaMessage(int, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent); method public void sendMultimediaMessage(int, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent); } @@ -54690,6 +54825,7 @@ package android.view.inputmethod { public final class InlineSuggestionsRequest implements android.os.Parcelable { method public int describeContents(); + method @NonNull public String getHostPackageName(); method public int getMaxSuggestionCount(); method @NonNull public java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs(); method public void writeToParcel(@NonNull android.os.Parcel, int); diff --git a/api/module-app-current.txt b/api/module-app-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/api/module-app-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/api/module-app-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt new file mode 100644 index 000000000000..0a6706550074 --- /dev/null +++ b/api/module-lib-current.txt @@ -0,0 +1,81 @@ +// Signature format: 2.0 +package android.app.timedetector { + + public final class PhoneTimeSuggestion implements android.os.Parcelable { + method public void addDebugInfo(@NonNull String); + method public void addDebugInfo(@NonNull java.util.List<java.lang.String>); + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getDebugInfo(); + method public int getPhoneId(); + method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR; + } + + public static final class PhoneTimeSuggestion.Builder { + ctor public PhoneTimeSuggestion.Builder(int); + method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String); + method @NonNull public android.app.timedetector.PhoneTimeSuggestion build(); + method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>); + } + + public class TimeDetector { + method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion); + } + +} + +package android.app.timezonedetector { + + public final class PhoneTimeZoneSuggestion implements android.os.Parcelable { + method public void addDebugInfo(@NonNull String); + method public void addDebugInfo(@NonNull java.util.List<java.lang.String>); + method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String); + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getDebugInfo(); + method public int getMatchType(); + method public int getPhoneId(); + method public int getQuality(); + method @Nullable public String getZoneId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR; + field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4 + field public static final int MATCH_TYPE_NA = 0; // 0x0 + field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3 + field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2 + field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5 + field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3 + field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2 + field public static final int QUALITY_NA = 0; // 0x0 + field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1 + } + + public static final class PhoneTimeZoneSuggestion.Builder { + ctor public PhoneTimeZoneSuggestion.Builder(int); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build(); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String); + } + + public class TimeZoneDetector { + method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion); + } + +} + +package android.os { + + public final class TimestampedValue<T> implements android.os.Parcelable { + ctor public TimestampedValue(long, @Nullable T); + method public int describeContents(); + method public long getReferenceTimeMillis(); + method @Nullable public T getValue(); + method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR; + } + +} + diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/api/system-current.txt b/api/system-current.txt index 367956c27721..71d1a381ec31 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -190,6 +190,7 @@ package android { field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES"; field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS"; field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; + field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED"; field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY"; field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"; field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION"; @@ -207,9 +208,11 @@ package android { field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"; + field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; + field public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS"; field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE"; field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"; field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS"; @@ -241,7 +244,6 @@ package android { field public static final int isVrOnly = 16844152; // 0x1010578 field public static final int requiredSystemPropertyName = 16844133; // 0x1010565 field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566 - field public static final int resourcesMap = 16844297; // 0x1010609 field public static final int supportsAmbientMode = 16844173; // 0x101058d field public static final int userRestriction = 16844164; // 0x1010584 } @@ -283,6 +285,7 @@ package android { field public static final int config_helpIntentNameKey = 17039390; // 0x104001e field public static final int config_helpPackageNameKey = 17039387; // 0x104001b field public static final int config_helpPackageNameValue = 17039388; // 0x104001c + field public static final int config_systemGallery = 17039402; // 0x104002a } public static final class R.style { @@ -325,6 +328,7 @@ package android.app { method public void setDeviceLocales(@NonNull android.os.LocaleList); method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); } public static interface ActivityManager.OnUidImportanceListener { @@ -813,7 +817,9 @@ package android.app.admin { field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION"; + field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE"; field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; + field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED"; field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME"; @@ -1330,6 +1336,10 @@ package android.app.usage { field public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService"; } + public class NetworkStatsManager { + method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.net.netstats.provider.NetworkStatsProviderCallback registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.AbstractNetworkStatsProvider); + } + public static final class UsageEvents.Event { method public int getInstanceId(); method @Nullable public String getNotificationChannelId(); @@ -1536,6 +1546,10 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); } + public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + } + public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile { method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); @@ -1544,12 +1558,20 @@ package android.bluetooth { field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; } + public final class BluetoothMap implements android.bluetooth.BluetoothProfile { + method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); + field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; + } + public final class BluetoothPan implements android.bluetooth.BluetoothProfile { method protected void finalize(); method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); method public boolean isTetheringOn(); method public void setBluetoothTethering(boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; field public static final int LOCAL_NAP_ROLE = 1; // 0x1 @@ -1672,7 +1694,6 @@ package android.content { } public abstract class Context { - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int); method public abstract android.content.Context createCredentialProtectedStorageContext(); method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; @@ -1695,6 +1716,7 @@ package android.content { field public static final String NETD_SERVICE = "netd"; field public static final String NETWORK_POLICY_SERVICE = "netpolicy"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; + field public static final String NETWORK_STACK_SERVICE = "network_stack"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; @@ -1781,6 +1803,7 @@ package android.content { field @Deprecated public static final String EXTRA_SIM_STATE = "ss"; field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; + field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000 field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED"; field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK"; @@ -2396,7 +2419,7 @@ package android.hardware.biometrics { package android.hardware.camera2 { public abstract class CameraDevice implements java.lang.AutoCloseable { - method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1 field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0 field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000 @@ -3546,7 +3569,6 @@ package android.hardware.soundtrigger { } public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { - method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR; field public final int end; @@ -3556,7 +3578,10 @@ package android.hardware.soundtrigger { public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR; + field public final int audioCapabilities; field @NonNull public final String description; field public final int id; field @NonNull public final String implementor; @@ -3567,6 +3592,7 @@ package android.hardware.soundtrigger { field public final int powerConsumptionMw; field public final int recognitionModes; field public final boolean returnsTriggerInEvent; + field @NonNull public final String supportedModelArch; field public final boolean supportsCaptureTransition; field public final boolean supportsConcurrentCapture; field @NonNull public final java.util.UUID uuid; @@ -4052,6 +4078,10 @@ package android.media { field public static final int ROLE_OUTPUT = 2; // 0x2 } + public final class AudioDeviceInfo { + field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19 + } + public final class AudioFocusInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.media.AudioAttributes getAttributes(); @@ -4079,6 +4109,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); @@ -4346,6 +4377,8 @@ package android.media.soundtrigger { method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean stopRecognition(); field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 } public abstract static class SoundTriggerDetector.Callback { @@ -4368,17 +4401,19 @@ package android.media.soundtrigger { method public int getDetectionServiceOperationsTimeout(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties(); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model); } public static class SoundTriggerManager.Model { - method public static android.media.soundtrigger.SoundTriggerManager.Model create(java.util.UUID, java.util.UUID, byte[]); - method public byte[] getModelData(); - method public java.util.UUID getModelUuid(); - method public java.util.UUID getVendorUuid(); + method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int); + method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]); + method @Nullable public byte[] getModelData(); + method @NonNull public java.util.UUID getModelUuid(); + method @NonNull public java.util.UUID getVendorUuid(); + method public int getVersion(); } } @@ -4508,6 +4543,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); + method @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPid(@NonNull String); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList(); method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList(); @@ -4519,6 +4555,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public void releaseTvInputHardware(int, android.media.tv.TvInputManager.Hardware); method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void removeBlockedRating(@NonNull android.media.tv.TvContentRating); method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void setParentalControlsEnabled(boolean); + field public static final int UNKNOWN_CLIENT_PID = -1; // 0xffffffff } public static final class TvInputManager.Hardware { @@ -4595,6 +4632,28 @@ package android.media.tv.tuner { method public abstract int getType(); } + public class Lnb implements java.lang.AutoCloseable { + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close(); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int sendDiseqcMessage(@NonNull byte[]); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setSatellitePosition(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setTone(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setVoltage(int); + field public static final int POSITION_A = 1; // 0x1 + field public static final int POSITION_B = 2; // 0x2 + field public static final int POSITION_UNDEFINED = 0; // 0x0 + field public static final int TONE_CONTINUOUS = 1; // 0x1 + field public static final int TONE_NONE = 0; // 0x0 + field public static final int VOLTAGE_11V = 2; // 0x2 + field public static final int VOLTAGE_12V = 3; // 0x3 + field public static final int VOLTAGE_13V = 4; // 0x4 + field public static final int VOLTAGE_14V = 5; // 0x5 + field public static final int VOLTAGE_15V = 6; // 0x6 + field public static final int VOLTAGE_18V = 7; // 0x7 + field public static final int VOLTAGE_19V = 8; // 0x8 + field public static final int VOLTAGE_5V = 1; // 0x1 + field public static final int VOLTAGE_NONE = 0; // 0x0 + } + public final class Tuner implements java.lang.AutoCloseable { ctor public Tuner(@NonNull android.content.Context); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Tuner.Descrambler openDescrambler(); @@ -4617,16 +4676,43 @@ package android.media.tv.tuner { field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4 field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2 field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8 + field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 + field public static final int RESULT_INVALID_STATE = 3; // 0x3 + field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2 + field public static final int RESULT_OUT_OF_MEMORY = 5; // 0x5 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public static final int RESULT_UNAVAILABLE = 1; // 0x1 + field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6 } } package android.media.tv.tuner.filter { + public abstract class FilterConfiguration { + field public static final int FILTER_TYPE_ALP = 16; // 0x10 + field public static final int FILTER_TYPE_IP = 4; // 0x4 + field public static final int FILTER_TYPE_MMTP = 2; // 0x2 + field public static final int FILTER_TYPE_TLV = 8; // 0x8 + field public static final int FILTER_TYPE_TS = 1; // 0x1 + } + public abstract class FilterEvent { ctor public FilterEvent(); } + public class PesSettings extends android.media.tv.tuner.filter.Settings { + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.PesSettings.Builder builder(@NonNull android.content.Context, int); + method public int getStreamId(); + method public boolean isRaw(); + } + + public static class PesSettings.Builder { + method @NonNull public android.media.tv.tuner.filter.PesSettings build(); + method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setRaw(boolean); + method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setStreamId(int); + } + public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent { method public int getDataLength(); method public int getSectionNumber(); @@ -4634,6 +4720,22 @@ package android.media.tv.tuner.filter { method public int getVersion(); } + public abstract class Settings { + } + + public class TsFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration { + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.TsFilterConfiguration.Builder builder(@NonNull android.content.Context); + method @Nullable public android.media.tv.tuner.filter.Settings getSettings(); + method public int getTpid(); + method public int getType(); + } + + public static class TsFilterConfiguration.Builder { + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration build(); + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setSettings(@NonNull android.media.tv.tuner.filter.Settings); + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setTpid(int); + } + } package android.metrics { @@ -4684,7 +4786,9 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { method public void logEvent(int, @NonNull String); + method public void reevaluateNetwork(); method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 @@ -4714,6 +4818,7 @@ package android.net { field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TYPE_NONE = -1; // 0xffffffff } public abstract static class ConnectivityManager.OnStartTetheringCallback { @@ -4849,12 +4954,36 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR; } + public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR; + } + public class Network implements android.os.Parcelable { ctor public Network(@NonNull android.net.Network); method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); field public final int netId; } + public final class NetworkAgentConfig implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getSubscriberId(); + method public boolean isNat64DetectionEnabled(); + method public boolean isProvisioningNotificationEnabled(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; + } + + public static class NetworkAgentConfig.Builder { + ctor public NetworkAgentConfig.Builder(); + method @NonNull public android.net.NetworkAgentConfig build(); + method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection(); + method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification(); + method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + } + public final class NetworkCapabilities implements android.os.Parcelable { method public boolean deduceRestrictedCapability(); method @NonNull public int[] getTransportTypes(); @@ -4913,6 +5042,9 @@ package android.net { field @Deprecated public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore"; field public static final String EXTRA_NEW_SCORER = "newScorer"; field @Deprecated public static final String EXTRA_PACKAGE_NAME = "packageName"; + field public static final int SCORE_FILTER_CURRENT_NETWORK = 1; // 0x1 + field public static final int SCORE_FILTER_NONE = 0; // 0x0 + field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2 } public static interface NetworkScoreManager.NetworkScoreCallback { @@ -5790,6 +5922,7 @@ package android.net.wifi { } public class ScanResult implements android.os.Parcelable { + ctor public ScanResult(); field public static final int CIPHER_CCMP = 3; // 0x3 field public static final int CIPHER_GCMP_256 = 4; // 0x4 field public static final int CIPHER_NONE = 0; // 0x0 @@ -5831,7 +5964,9 @@ package android.net.wifi { public final class SoftApConfiguration implements android.os.Parcelable { method public int describeContents(); + method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList(); method public int getBand(); + method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList(); method @Nullable public android.net.MacAddress getBssid(); method public int getChannel(); method public int getMaxNumberOfClients(); @@ -5839,6 +5974,7 @@ package android.net.wifi { method public int getSecurityType(); method public int getShutdownTimeoutMillis(); method @Nullable public String getSsid(); + method public boolean isClientControlByUserEnabled(); method public boolean isHiddenSsid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int BAND_2GHZ = 1; // 0x1 @@ -5856,9 +5992,11 @@ package android.net.wifi { ctor public SoftApConfiguration.Builder(); ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration); method @NonNull public android.net.wifi.SoftApConfiguration build(); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int); @@ -5901,6 +6039,7 @@ package android.net.wifi { method @Deprecated public static boolean isMetered(@Nullable android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiInfo); method @Deprecated public boolean isNoInternetAccessExpected(); method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration); + method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus); method @Deprecated public void setProxy(@NonNull android.net.IpConfiguration.ProxySettings, @NonNull android.net.ProxyInfo); field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0 field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1 @@ -5945,6 +6084,7 @@ package android.net.wifi { method @Deprecated public boolean getHasEverConnected(); method @Deprecated @Nullable public static String getNetworkDisableReasonString(int); method @Deprecated public int getNetworkSelectionDisableReason(); + method @Deprecated public int getNetworkSelectionStatus(); method @Deprecated @NonNull public String getNetworkStatusString(); method @Deprecated public boolean isNetworkEnabled(); method @Deprecated public boolean isNetworkPermanentlyDisabled(); @@ -5959,6 +6099,16 @@ package android.net.wifi { field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4 field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa field @Deprecated public static final int NETWORK_SELECTION_ENABLE = 0; // 0x0 + field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0 + field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2 + field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1 + } + + @Deprecated public static final class WifiConfiguration.NetworkSelectionStatus.Builder { + ctor @Deprecated public WifiConfiguration.NetworkSelectionStatus.Builder(); + method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus build(); + method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionDisableReason(int); + method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionStatus(int); } @Deprecated public static class WifiConfiguration.RecentFailure { @@ -6003,6 +6153,15 @@ package android.net.wifi { field public static final int INVALID_RSSI = -127; // 0xffffff81 } + public static final class WifiInfo.Builder { + ctor public WifiInfo.Builder(); + method @NonNull public android.net.wifi.WifiInfo build(); + method @NonNull public android.net.wifi.WifiInfo.Builder setBssid(@NonNull String); + method @NonNull public android.net.wifi.WifiInfo.Builder setNetworkId(int); + method @NonNull public android.net.wifi.WifiInfo.Builder setRssi(int); + method @NonNull public android.net.wifi.WifiInfo.Builder setSsid(@NonNull byte[]); + } + public class WifiManager { method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean); @@ -6026,6 +6185,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>); method public boolean isApMacRandomizationSupported(); method public boolean isConnectedMacRandomizationSupported(); method @Deprecated public boolean isDeviceToDeviceRttSupported(); @@ -6096,6 +6256,8 @@ package android.net.wifi { field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0 field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1 + field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0 + field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1 field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0 field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1 field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2 @@ -6137,6 +6299,7 @@ package android.net.wifi { } public static interface WifiManager.SoftApCallback { + method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int); method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability); method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>); method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo); @@ -6170,6 +6333,25 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int); } + public final class WifiOemConfigStoreMigrationHook { + method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load(); + } + + public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations(); + method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR; + } + + public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder { + ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder(); + method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build(); + method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>); + method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration); + } + public class WifiScanner { method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]); method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings); @@ -7272,7 +7454,6 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(); @@ -8026,7 +8207,6 @@ package android.provider { field @NonNull public static final String ENABLE_CMAS_PRESIDENTIAL_PREF = "enable_cmas_presidential_alerts"; field @NonNull public static final String ENABLE_CMAS_SEVERE_THREAT_PREF = "enable_cmas_severe_threat_alerts"; field @NonNull public static final String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts"; - field @NonNull public static final String ENABLE_FULL_VOLUME_PREF = "use_full_volume"; field @NonNull public static final String ENABLE_PUBLIC_SAFETY_PREF = "enable_public_safety_messages"; field @NonNull public static final String ENABLE_STATE_LOCAL_TEST_PREF = "enable_state_local_test_alerts"; field @NonNull public static final String ENABLE_TEST_ALERT_PREF = "enable_test_alerts"; @@ -8661,7 +8841,10 @@ package android.service.notification { method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel); method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationVisibilityChanged(@NonNull String, boolean); method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>); + method public void onPanelHidden(); + method public void onPanelRevealed(int); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; @@ -9259,6 +9442,7 @@ package android.telephony { public final class CallQuality implements android.os.Parcelable { ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int); + ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean); method public int describeContents(); method public int getAverageRelativeJitter(); method public int getAverageRoundTripTime(); @@ -9271,6 +9455,9 @@ package android.telephony { method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); method public int getUplinkCallQualityLevel(); + method public boolean isIncomingSilenceDetected(); + method public boolean isOutgoingSilenceDetected(); + method public boolean isRtpInactivityDetected(); method public void writeToParcel(android.os.Parcel, int); field public static final int CALL_QUALITY_BAD = 4; // 0x4 field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0 @@ -10270,9 +10457,19 @@ package android.telephony { method public boolean disableCellBroadcastRange(int, int, int); method public boolean enableCellBroadcastRange(int, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc(); method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int); + field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3 + field public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; // 0x1 + field public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; // 0x2 + field public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; // 0x0 + } + + public class SmsMessage { + method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static byte[] getSubmitPduEncodedMessage(boolean, @NonNull String, @NonNull String, int, int, int, int, int, int); } public class SubscriptionInfo implements android.os.Parcelable { @@ -10382,6 +10579,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode(); method public int getEmergencyNumberDbVersion(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain(); + method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping(); method public int getMaxNumberOfSimultaneouslyActiveSims(); @@ -10410,6 +10608,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int); method public boolean isCurrentSimOperator(@NonNull String, int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionEnabled(); method public boolean isDataConnectivityPossible(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled(); @@ -10465,12 +10664,16 @@ package android.telephony { method public void updateServiceLocation(); method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String); field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED"; + field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"; + field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE"; + field public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"; + field public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED"; + field public static final String ACTION_CARRIER_SIGNAL_RESET = "com.android.internal.telephony.CARRIER_SIGNAL_RESET"; field public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"; field public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED"; field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE"; field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED"; - field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME"; field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE"; field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS"; field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED"; @@ -10482,6 +10685,17 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; + field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto"; + field public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt"; + field @Deprecated public static final String EXTRA_APN_TYPE = "apnType"; + field public static final String EXTRA_APN_TYPE_INT = "apnTypeInt"; + field public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable"; + field public static final String EXTRA_ERROR_CODE = "errorCode"; + field public static final String EXTRA_PCO_ID = "pcoId"; + field public static final String EXTRA_PCO_VALUE = "pcoValue"; + field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE"; + field public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = "android.telephony.extra.PHONE_IN_EMERGENCY_CALL"; + field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl"; field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; @@ -11086,6 +11300,7 @@ package android.telephony.ims { public class ImsManager { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); + field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; } public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { @@ -11380,13 +11595,29 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19 + field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13 + field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12 + field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14 + field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11 + field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17 + field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16 + field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15 + field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10 + field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf + field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7 field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a + field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa + field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb + field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; @@ -11399,6 +11630,47 @@ package android.telephony.ims { method public void onProvisioningStringChanged(int, @NonNull String); } + public final class RcsContactUceCapability implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags(); + method @NonNull public android.net.Uri getContactUri(); + method @Nullable public android.net.Uri getServiceUri(int); + method public boolean isCapable(int); + method public boolean isCapable(@NonNull String); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2 + field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4 + field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1 + field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000 + field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8 + field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40 + field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80 + field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20 + field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10 + field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000 + field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000 + field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000 + field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000 + field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100 + field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000 + field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000 + field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000 + field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000 + field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000 + field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800 + field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400 + field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200 + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR; + } + + public static class RcsContactUceCapability.Builder { + ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri); + method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int, @NonNull android.net.Uri); + method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int); + method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String); + method @NonNull public android.telephony.ims.RcsContactUceCapability build(); + } + public interface RegistrationManager { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); diff --git a/api/test-current.txt b/api/test-current.txt index d017dd63bdd3..1db4c9b82343 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -34,6 +34,7 @@ package android { public static final class R.string { field public static final int config_defaultAssistant = 17039393; // 0x1040021 field public static final int config_defaultDialer = 17039395; // 0x1040023 + field public static final int config_systemGallery = 17039402; // 0x104002a } } @@ -435,6 +436,8 @@ package android.app { } public class StatusBarManager { + method public void collapsePanels(); + method public void expandNotificationsPanel(); method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo(); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean); @@ -754,6 +757,7 @@ package android.content { field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture"; field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; + field public static final String NETWORK_STACK_SERVICE = "network_stack"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String POWER_WHITELIST_MANAGER = "power_whitelist"; field public static final String ROLLBACK_SERVICE = "rollback"; @@ -1018,7 +1022,7 @@ package android.graphics.drawable { package android.hardware.camera2 { public abstract class CameraDevice implements java.lang.AutoCloseable { - method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1 field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0 field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000 @@ -1494,7 +1498,9 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { method public void logEvent(int, @NonNull String); + method public void reevaluateNetwork(); method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 @@ -2731,6 +2737,11 @@ package android.service.autofill { method @Nullable public android.util.SparseArray<android.service.autofill.InternalOnClickAction> getActions(); } + public static final class Dataset.Builder { + ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); + } + public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation { method public void apply(@NonNull android.service.autofill.ValueFinder, @NonNull android.widget.RemoteViews, int) throws java.lang.Exception; } @@ -2929,7 +2940,10 @@ package android.service.notification { method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel); method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationVisibilityChanged(@NonNull String, boolean); method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>); + method public void onPanelHidden(); + method public void onPanelRevealed(int); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; @@ -3122,6 +3136,7 @@ package android.telephony { public final class CallQuality implements android.os.Parcelable { ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int); + ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean); method public int describeContents(); method public int getAverageRelativeJitter(); method public int getAverageRoundTripTime(); @@ -3134,6 +3149,9 @@ package android.telephony { method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); method public int getUplinkCallQualityLevel(); + method public boolean isIncomingSilenceDetected(); + method public boolean isOutgoingSilenceDetected(); + method public boolean isRtpInactivityDetected(); method public void writeToParcel(android.os.Parcel, int); field public static final int CALL_QUALITY_BAD = 4; // 0x4 field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0 @@ -3517,6 +3535,7 @@ package android.telephony.ims { public class ImsManager { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); + field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; } public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { @@ -3807,13 +3826,29 @@ package android.telephony.ims { method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public int getProvisioningIntValue(int); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int); + method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19 + field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13 + field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12 + field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14 + field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11 + field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17 + field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16 + field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15 + field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10 + field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf + field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7 field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a + field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa + field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb + field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; @@ -4244,6 +4279,7 @@ package android.util { field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override."; field public static final String FFLAG_PREFIX = "sys.fflag."; field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; + field public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req"; field public static final String PERSIST_PREFIX = "persist.sys.fflag.override."; field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer"; diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 459520a3eb27..8fac31a05c49 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -1113,7 +1113,7 @@ void BootAnimation::handleViewport(nsecs_t timestep) { SurfaceComposerClient::Transaction t; t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset) .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight)); - t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect); + t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect); t.apply(); mTargetInset = mCurrentInset = 0; diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 229628c7dd8b..407478945151 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -33,6 +33,8 @@ namespace android::idmap2 { namespace { +#define REWRITE_PACKAGE(resid, package_id) \ + (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U)) #define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24) std::string ConcatPolicies(const std::vector<std::string>& policies) { @@ -154,6 +156,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage return Error("root element is not <overlay> tag"); } + const uint8_t target_package_id = target_package->GetPackageId(); const uint8_t overlay_package_id = overlay_package->GetPackageId(); auto overlay_it_end = root_it.end(); for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) { @@ -187,6 +190,9 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage continue; } + // Retrieve the compile-time resource id of the target resource. + target_id = REWRITE_PACKAGE(target_id, target_package_id); + if (overlay_resource->dataType == Res_value::TYPE_STRING) { overlay_resource->data += string_pool_offset; } @@ -214,6 +220,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy( const AssetManager2* target_am, const AssetManager2* overlay_am, const LoadedPackage* target_package, const LoadedPackage* overlay_package) { ResourceMapping resource_mapping; + const uint8_t target_package_id = target_package->GetPackageId(); const auto end = overlay_package->end(); for (auto iter = overlay_package->begin(); iter != end; ++iter) { const ResourceId overlay_resid = *iter; @@ -225,11 +232,14 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy( // Find the resource with the same type and entry name within the target package. const std::string full_name = base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str()); - const ResourceId target_resource = target_am->GetResourceId(full_name); + ResourceId target_resource = target_am->GetResourceId(full_name); if (target_resource == 0U) { continue; } + // Retrieve the compile-time resource id of the target resource. + target_resource = REWRITE_PACKAGE(target_resource, target_package_id); + resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid, /* rewrite_overlay_reference */ false); } diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS index 04464ce02b4f..a61babf32e58 100644 --- a/cmds/statsd/OWNERS +++ b/cmds/statsd/OWNERS @@ -1,7 +1,8 @@ -jianjin@google.com +jeffreyhuang@google.com joeo@google.com jtnguyen@google.com muhammadq@google.com +ruchirr@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 6fc1e236c661..967fd323e5a0 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -261,6 +261,11 @@ inline Matcher getSimpleMatcher(int32_t tag, size_t field) { return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000); } +inline Matcher getFirstUidMatcher(int32_t atomId) { + int32_t pos[] = {1, 1, 1}; + return Matcher(Field(atomId, pos, 2), 0xff7f7f7f); +} + /** * A wrapper for a union type to contain multiple types of values. * diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto index 6d2bd04756ac..946c55087005 100644 --- a/cmds/statsd/src/atom_field_options.proto +++ b/cmds/statsd/src/atom_field_options.proto @@ -30,6 +30,8 @@ enum StateField { PRIMARY = 1; // The field that represents the state. It's an exclusive state. EXCLUSIVE = 2; + + PRIMARY_FIELD_FIRST_UID = 3; } // Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 19b9709e1d41..4e57c9cdf806 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -126,10 +126,10 @@ message Atom { AppStartOccurred app_start_occurred = 48; AppStartCanceled app_start_canceled = 49; AppStartFullyDrawn app_start_fully_drawn = 50; - LmkKillOccurred lmk_kill_occurred = 51; + LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"]; PictureInPictureStateChanged picture_in_picture_state_changed = 52; WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"]; - LmkStateChanged lmk_state_changed = 54; + LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"]; AppStartMemoryStateCaptured app_start_memory_state_captured = 55; ShutdownSequenceReported shutdown_sequence_reported = 56; BootSequenceReported boot_sequence_reported = 57; @@ -333,10 +333,15 @@ message Atom { MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"]; MediaProviderIdleMaintenance media_provider_idle_maintenance = 237 [(module) = "mediaprovider"]; + RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238; + BootTimeEventDuration boot_time_event_duration_reported = 239; + BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240; + BootTimeEventUtcTime boot_time_event_utc_time_reported = 241; + BootTimeEventErrorCode boot_time_event_error_code_reported = 242; } // Pulled events will start at field 10000. - // Next: 10068 + // Next: 10070 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -405,6 +410,8 @@ message Atom { VmsClientStats vms_client_stats = 10065; NotificationRemoteViews notification_remote_views = 10066; DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067; + GraphicsStats graphics_stats = 10068; + RuntimeAppOpsAccess runtime_app_ops_access = 10069; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -906,14 +913,16 @@ message CameraStateChanged { * TODO */ message WakelockStateChanged { - repeated AttributionNode attribution_node = 1; + repeated AttributionNode attribution_node = 1 + [(state_field_option).option = PRIMARY_FIELD_FIRST_UID]; // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock. // From frameworks/base/core/proto/android/os/enums.proto. - optional android.os.WakeLockLevelEnum type = 2; + optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY]; + ; // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). - optional string tag = 3; + optional string tag = 3 [(state_field_option).option = PRIMARY]; enum State { RELEASE = 0; @@ -921,7 +930,7 @@ message WakelockStateChanged { CHANGE_RELEASE = 2; CHANGE_ACQUIRE = 3; } - optional State state = 4; + optional State state = 4 [(state_field_option).option = EXCLUSIVE]; } /** @@ -3923,6 +3932,207 @@ message MediaProviderIdleMaintenance { optional float normalized_expired_media = 5; } +/** + * Represents boot time event with duration in ms. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventDuration { + enum DurationEvent { + UNKNOWN = 0; + // Bootloader time excluding BOOTLOADER_UI_WAIT + boot complete time. Logged from bootstat. + ABSOLUTE_BOOT_TIME = 1; + // Bootloader's 1st stage execution time. + // Logged from bootstat. + BOOTLOADER_FIRST_STAGE_EXEC = 2; + // Bootloader's 1st stage loading time. + // Logged from bootstat. + BOOTLOADER_FIRST_STAGE_LOAD = 3; + // Bootloader's kernel loading time. + // Logged from bootstat. + BOOTLOADER_KERNEL_LOAD = 4; + // Bootloader's 2nd stage execution time. + // Logged from bootstat. + BOOTLOADER_SECOND_STAGE_EXEC = 5; + // Bootloader's 2nd stage loading time. + // Logged from bootstat. + BOOTLOADER_SECOND_STAGE_LOAD = 6; + // Duration for Bootloader to show unlocked device's warning UI. This should not happen + // for locked device. + // Logged from bootstat. + BOOTLOADER_UI_WAIT = 7; + // Total time spend in bootloader. This is the sum of all BOOTLOADER_* listed above. + // Logged from bootstat. + BOOTLOADER_TOTAL = 8; + // Shutdown duration inside init for the reboot before the current boot up. + // Logged from f/b/services/.../BootReceiver.java. + SHUTDOWN_DURATION = 9; + // Total time for mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_DEFAULT_DURATION = 10; + // Total time for early stage mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_EARLY_DURATION = 11; + // Total time for late stage mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_LATE_DURATION = 12; + // Average time to scan non-system app after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_INIT_TIME = 13; + // Time to initialize Package manager after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME = 14; + // Time to scan all system app from Package manager after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME = 15; + // Init's total time for cold boot stage. + // Logged from bootstat. + COLDBOOT_WAIT = 16; + // Init's total time for initializing selinux. + // Logged from bootstat. + SELINUX_INIT = 17; + // Time since last factory reset. + // Logged from bootstat. + FACTORY_RESET_TIME_SINCE_RESET = 18; + } + + // Type of the event. + optional DurationEvent event = 1; + // Duration of the event in ms. + optional int64 duration_millis = 2; +} + +/** + * Represents the start of specific boot time event during bootup in ms. This is usually a time + * since boot-up. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventElapsedTime { + enum ElapsedTimeEvent { + UNKNOWN = 0; + // Time when init starts 1st stage. Logged from bootstat. + ANDROID_INIT_STAGE_1 = 1; + // Time when sys.boot_completed prop is set. + // Logged from bootstat. + BOOT_COMPLETE = 2; + // BOOT_COMPLETE for encrypted device. + BOOT_COMPLETE_ENCRYPTION = 3; + // BOOT_COMPLETE for device with no encryption. + BOOT_COMPLETE_NO_ENCRYPTION = 4; + // Adjusted BOOT_COMPLETE for encrypted device extracting decryption time. + BOOT_COMPLETE_POST_DESCRYPT = 5; + // BOOT_COMPLETE after factory reset. + FACTORY_RESET_BOOT_COMPLETE = 6; + // BOOT_COMPLETE_NO_ENCRYPTION after factory reset. + FACTORY_RESET_BOOT_COMPLETE_NO_ENCRYPTION = 7; + // BOOT_COMPLETE_POST_DESCRYPT after factory reset. + FACTORY_RESET_BOOT_COMPLETE_POST_DESCRYPT = 8; + // BOOT_COMPLETE after OTA. + OTA_BOOT_COMPLETE = 9; + // BOOT_COMPLETE_NO_ENCRYPTION after OTA. + OTA_BOOT_COMPLETE_NO_ENCRYPTION = 10; + // BOOT_COMPLETE_POST_DESCRYPT after OTA. + OTA_BOOT_COMPLETE_POST_DESCRYPT = 11; + // Time when the system starts sending LOCKED_BOOT_COMPLETED broadcast. + // Logged from f/b/services/.../UserController.java + FRAMEWORK_LOCKED_BOOT_COMPLETED = 12; + // Time when the system starts sending BOOT_COMPLETED broadcast. + // Logged from f/b/services/.../UserController.java + FRAMEWORK_BOOT_COMPLETED = 13; + // Time when the package manager starts init. + // Logged from f/b/services/.../SystemServer.java + PACKAGE_MANAGER_INIT_START = 14; + // Time when package manager is ready + // Logged from f/b/services/.../SystemServer.java + PACKAGE_MANAGER_INIT_READY = 15; + // Represents the time when user has entered unlock credential for system with user pin. + // Logged from bootstat. + POST_DECRYPT = 16; + // Represents the start of zygote's init. + // Logged from zygote itself. + ZYGOTE_INIT_START = 17; + // Represents the start of secondary zygote's init. + // TODO: add logging to zygote + SECONDARY_ZYGOTE_INIT_START = 18; + // Represents the start of system server's init. + // Logged from f/b/services/.../SystemServer.java + SYSTEM_SERVER_INIT_START = 19; + // Represents the completion of system server's init. + // Logged from f/b/services/.../SystemServer.java + SYSTEM_SERVER_READY = 20; + // Represents the start of launcher during boot-up. + // TODO: add logging + LAUNCHER_START = 21; + // Represents the completion of launcher's initial rendering. User can use other apps from + // launcher from this point. + // TODO: add logging + LAUNCHER_SHOWN = 22; + } + + // Type of the event. + optional ElapsedTimeEvent event = 1; + // Time since bootup for the event. + // It should be acquired from SystemClock elapsedRealtime() call or equivalent. + optional int64 time_millis = 2; +} + +/** + * Boot time events with UTC time. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventUtcTime { + enum UtcTimeEvent { + UNKNOWN = 0; + // Time of the bootstat's marking of 1st boot after the last factory reset. + // Logged from bootstat. + FACTORY_RESET_RESET_TIME = 1; + // The time when bootstat records FACTORY_RESET_* events. This is close to + // BOOT_COMPLETE time for the current bootup. + // Logged from bootstat. + FACTORY_RESET_CURRENT_TIME = 2; + // DUplicate of FACTORY_RESET_RESET_TIME added for debugging purpose. + // Logged from bootstat. + FACTORY_RESET_RECORD_VALUE = 3; + } + + // Type of the event. + optional UtcTimeEvent event = 1; + // UTC time for the event. + optional int64 utc_time_secs = 2; +} + +/** + * Boot time events representing specific error code during bootup. + * Meaning of error code can be different per each event type. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventErrorCode { + enum ErrorCodeEvent { + UNKNOWN = 0; + // Linux error code for time() call to get the current UTC time. + // Logged from bootstat. + FACTORY_RESET_CURRENT_TIME_FAILURE = 1; + // Represents UmountStat before the reboot for the current boot up. Error codes defined + // as UMOUNT_STAT_* from init/reboot.cpp. + // Logged from f/b/services/.../BootReceiver.java. + SHUTDOWN_UMOUNT_STAT = 2; + // Reprepsents fie system mounting error code for the current boot. Error codes defined + // as combination of FsStatFlags from system/core/fs_mgr/fs_mgr.cpp. + // Logged from f/b/services/.../BootReceiver.java. + FS_MGR_FS_STAT = 3; + } + + // Type of the event. + optional ErrorCodeEvent event = 1; + // error code defined per each event type. + // For example, this can have a value of FsStatFlags.FS_STAT_FULL_MOUNT_FAILED for the event of + // FS_MGR_FS_STAT. + optional int32 error_code = 2; +} + ////////////////////////////////////////////////////////////////////// // Pulled atoms below this line // ////////////////////////////////////////////////////////////////////// @@ -4723,36 +4933,69 @@ message RoleHolder { } message AggStats { - optional int64 min = 1; - - optional int64 average = 2; - - optional int64 max = 3; -} - + // These are all in byte resolution. + optional int64 min = 1 [deprecated = true]; + optional int64 average = 2 [deprecated = true]; + optional int64 max = 3 [deprecated = true]; + + // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above + // int64 fields. + optional int32 mean_kb = 4; + optional int32 max_kb = 5; +} + +// A reduced subset of process states; reducing the number of possible states allows more +// aggressive device-side aggregation of statistics and hence reduces metric upload size. +enum ProcessStateAggregated { + PROCESS_STATE_UNKNOWN = 0; + // Persistent system process. + PROCESS_STATE_PERSISTENT = 1; + // Top activity; actually any visible activity. + PROCESS_STATE_TOP = 2; + // Process binding to top or a foreground service. + PROCESS_STATE_BOUND_TOP_OR_FGS = 3; + // Processing running a foreground service. + PROCESS_STATE_FGS = 4; + // Important foreground process (ime, wallpaper, etc). + PROCESS_STATE_IMPORTANT_FOREGROUND = 5; + // Important background process. + PROCESS_STATE_BACKGROUND = 6; + // Process running a receiver. + PROCESS_STATE_RECEIVER = 7; + // All kinds of cached processes. + PROCESS_STATE_CACHED = 8; +} + +// Next tag: 13 message ProcessStatsStateProto { optional android.service.procstats.ScreenState screen_state = 1; - optional android.service.procstats.MemoryState memory_state = 2; + optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true]; // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java // and not frameworks/base/core/java/android/app/ActivityManager.java - optional android.service.procstats.ProcessState process_state = 3; + optional android.service.procstats.ProcessState process_state = 3 [deprecated = true]; + + optional ProcessStateAggregated process_state_aggregated = 10; // Millisecond uptime duration spent in this state - optional int64 duration_millis = 4; + optional int64 duration_millis = 4 [deprecated = true]; + // Same as above, but with minute resolution so it fits into an int32. + optional int32 duration_minutes = 11; // Millisecond elapsed realtime duration spent in this state - optional int64 realtime_duration_millis = 9; + optional int64 realtime_duration_millis = 9 [deprecated = true]; + // Same as above, but with minute resolution so it fits into an int32. + optional int32 realtime_duration_minutes = 12; // # of samples taken optional int32 sample_size = 5; // PSS is memory reserved for this process - optional AggStats pss = 6; + optional AggStats pss = 6 [deprecated = true]; // USS is memory shared between processes, divided evenly for accounting - optional AggStats uss = 7; + optional AggStats uss = 7 [deprecated = true]; // RSS is memory resident for this process optional AggStats rss = 8; @@ -4777,7 +5020,7 @@ message ProcessStatsProto { // PSS stats during cached kill optional AggStats cached_pss = 3; } - optional Kill kill = 3; + optional Kill kill = 3 [deprecated = true]; // Time and memory spent in various states. repeated ProcessStatsStateProto states = 5; @@ -6844,7 +7087,7 @@ message AppOps { // Uid of the package requesting the op optional int32 uid = 1 [(is_uid) = true]; - // Nmae of the package performing the op + // Name of the package performing the op optional string package_name = 2; // operation id; maps to the OP_* constants in AppOpsManager.java @@ -7338,6 +7581,17 @@ message UpdateEngineSuccessfulUpdateReported { } /** + * Reported when the RebootEscrow HAL has attempted to recover the escrowed + * key to indicate whether it was successful or not. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java + */ +message RebootEscrowRecoveryReported { + optional bool successful = 1; +} + +/** * Global display pipeline metrics reported by SurfaceFlinger. * Pulled from: * frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp @@ -7553,3 +7807,101 @@ message DangerousPermissionStateSampled { optional int32 permission_flags = 4; } +/** + * HWUI renders pipeline type: GL (0) or Vulkan (1). + */ +enum PipelineType { + GL = 0; + VULKAN = 1; +} + +/** + * HWUI stats for a given app. + */ +message GraphicsStats { + // The package name of the app + optional string package_name = 1; + + // The version code of the app + optional int64 version_code = 2; + + // The start & end timestamps in UTC as + // milliseconds since January 1, 1970 + // Compatible with java.util.Date#setTime() + optional int64 stats_start = 3; + + optional int64 stats_end = 4; + + // HWUI renders pipeline type: GL or Vulkan. + optional PipelineType pipeline = 5; + + // Distinct frame count. + optional int32 total_frames = 6; + + // Number of "missed vsync" events. + optional int32 missed_vsync_count = 7; + + // Number of frames in triple-buffering scenario (high input latency) + optional int32 high_input_latency_count = 8; + + // Number of "slow UI thread" events. + optional int32 slow_ui_thread_count = 9; + + // Number of "slow bitmap upload" events. + optional int32 slow_bitmap_upload_count = 10; + + // Number of "slow draw" events. + optional int32 slow_draw_count = 11; + + // Number of frames that missed their deadline (aka, visibly janked) + optional int32 missed_deadline_count = 12; + + // The frame time histogram for the package + optional FrameTimingHistogram cpu_histogram = 13 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // The gpu frame time histogram for the package + optional FrameTimingHistogram gpu_histogram = 14 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // UI mainline module version. + optional int64 version_ui_module = 15; + + // If true, these are HWUI stats for up to a 24h period for a given app from today. + // If false, these are HWUI stats for a 24h period for a given app from the last complete + // day (yesterday). Stats from yesterday stay constant, while stats from today may change as + // more apps are running / rendering. + optional bool is_today = 16; +} + +/** + * Message related to dangerous (runtime) app ops access + */ +message RuntimeAppOpsAccess { + // Uid of the package accessing app op + optional int32 uid = 1 [(is_uid) = true]; + + // Name of the package accessing app op + optional string package_name = 2; + + // operation id; maps to the OP_* constants in AppOpsManager.java + optional int32 op_id = 3; + + // feature id; provided by developer when accessing related API, limited at 50 chars by API. + // Features must be provided through manifest using <feature> tag available in R and above. + optional string feature_id = 4; + + // message related to app op access, limited to 600 chars by API + optional string message = 5; + + enum SamplingStrategy { + DEFAULT = 0; + UNIFORM = 1; + RARELY_USED = 2; + } + + // sampling strategy used to collect this message + optional SamplingStrategy sampling_strategy = 6; +} + + diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 1d31873209b2..731afe8c0859 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -59,161 +59,171 @@ namespace statsd { const int64_t NO_ALARM_UPDATE = INT64_MAX; std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { - // wifi_bytes_transfer - {{.atomTag = android::util::WIFI_BYTES_TRANSFER}, - {.additiveFields = {2, 3, 4, 5}, - .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}}, - // wifi_bytes_transfer_by_fg_bg - {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG}, - {.additiveFields = {3, 4, 5, 6}, - .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}}, - // mobile_bytes_transfer - {{.atomTag = android::util::MOBILE_BYTES_TRANSFER}, - {.additiveFields = {2, 3, 4, 5}, - .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}}, - // mobile_bytes_transfer_by_fg_bg - {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG}, - {.additiveFields = {3, 4, 5, 6}, - .puller = - new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}}, - // bluetooth_bytes_transfer - {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER}, - {.additiveFields = {2, 3}, - .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}}, + // kernel_wakelock {{.atomTag = android::util::KERNEL_WAKELOCK}, {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}}, + // subsystem_sleep_state {{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE}, {.puller = new SubsystemSleepStatePuller()}}, + // on_device_power_measurement {{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT}, {.puller = new PowerStatsPuller()}}, + // cpu_time_per_freq {{.atomTag = android::util::CPU_TIME_PER_FREQ}, {.additiveFields = {3}, .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}}, + // cpu_time_per_uid {{.atomTag = android::util::CPU_TIME_PER_UID}, {.additiveFields = {2, 3}, .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}}, + // cpu_time_per_uid_freq // the throttling is 3sec, handled in // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ}, {.additiveFields = {4}, .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}}, + // cpu_active_time // the throttling is 3sec, handled in // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader {{.atomTag = android::util::CPU_ACTIVE_TIME}, {.additiveFields = {2}, .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}}, + // cpu_cluster_time // the throttling is 3sec, handled in // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader {{.atomTag = android::util::CPU_CLUSTER_TIME}, {.additiveFields = {3}, .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}}, + // wifi_activity_energy_info {{.atomTag = android::util::WIFI_ACTIVITY_INFO}, {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}}, + // modem_activity_info {{.atomTag = android::util::MODEM_ACTIVITY_INFO}, {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}}, - // bluetooth_activity_info - {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO}, - {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}}, + // system_elapsed_realtime {{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME}, {.coolDownNs = NS_PER_SEC, .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME), .pullTimeoutNs = NS_PER_SEC / 2, }}, + // system_uptime {{.atomTag = android::util::SYSTEM_UPTIME}, {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}}, + // remaining_battery_capacity {{.atomTag = android::util::REMAINING_BATTERY_CAPACITY}, {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}}, + // full_battery_capacity {{.atomTag = android::util::FULL_BATTERY_CAPACITY}, {.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}}, + // battery_voltage {{.atomTag = android::util::BATTERY_VOLTAGE}, {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}}, + // battery_level {{.atomTag = android::util::BATTERY_LEVEL}, {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}}, + // battery_cycle_count {{.atomTag = android::util::BATTERY_CYCLE_COUNT}, {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}}, + // process_memory_state {{.atomTag = android::util::PROCESS_MEMORY_STATE}, {.additiveFields = {4, 5, 6, 7, 8}, .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}}, + // process_memory_high_water_mark {{.atomTag = android::util::PROCESS_MEMORY_HIGH_WATER_MARK}, {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}}, + // process_memory_snapshot {{.atomTag = android::util::PROCESS_MEMORY_SNAPSHOT}, {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}}, + // system_ion_heap_size {{.atomTag = android::util::SYSTEM_ION_HEAP_SIZE}, {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}}, + // process_system_ion_heap_size {{.atomTag = android::util::PROCESS_SYSTEM_ION_HEAP_SIZE}, {.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}}, + // temperature {{.atomTag = android::util::TEMPERATURE}, {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}}, + // cooling_device {{.atomTag = android::util::COOLING_DEVICE}, {.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}}, + // binder_calls {{.atomTag = android::util::BINDER_CALLS}, {.additiveFields = {4, 5, 6, 8, 12}, .puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}}, + // binder_calls_exceptions {{.atomTag = android::util::BINDER_CALLS_EXCEPTIONS}, {.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}}, + // looper_stats {{.atomTag = android::util::LOOPER_STATS}, {.additiveFields = {5, 6, 7, 8, 9}, .puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}}, + // Disk Stats {{.atomTag = android::util::DISK_STATS}, {.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}}, + // Directory usage {{.atomTag = android::util::DIRECTORY_USAGE}, {.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}}, + // Size of app's code, data, and cache {{.atomTag = android::util::APP_SIZE}, {.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}}, + // Size of specific categories of files. Eg. Music. {{.atomTag = android::util::CATEGORY_SIZE}, {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}}, + // Number of fingerprints enrolled for each user. {{.atomTag = android::util::NUM_FINGERPRINTS_ENROLLED}, {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}}, + // Number of faces enrolled for each user. {{.atomTag = android::util::NUM_FACES_ENROLLED}, {.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}}, + // ProcStats. {{.atomTag = android::util::PROC_STATS}, {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}}, + // ProcStatsPkgProc. {{.atomTag = android::util::PROC_STATS_PKG_PROC}, {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}}, + // Disk I/O stats per uid. {{.atomTag = android::util::DISK_IO}, {.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, .coolDownNs = 3 * NS_PER_SEC, .puller = new StatsCompanionServicePuller(android::util::DISK_IO)}}, - // PowerProfile constants for power model calculations. - {{.atomTag = android::util::POWER_PROFILE}, - {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}}, + // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses. {{.atomTag = android::util::PROCESS_CPU_TIME}, {.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/, @@ -221,64 +231,83 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {{.atomTag = android::util::CPU_TIME_PER_THREAD_FREQ}, {.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21}, .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}}, + // DeviceCalculatedPowerUse. {{.atomTag = android::util::DEVICE_CALCULATED_POWER_USE}, {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}}, + // DeviceCalculatedPowerBlameUid. {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_UID}, {.puller = new StatsCompanionServicePuller( android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}}, + // DeviceCalculatedPowerBlameOther. {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER}, {.puller = new StatsCompanionServicePuller( android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}}, + // DebugElapsedClock. {{.atomTag = android::util::DEBUG_ELAPSED_CLOCK}, {.additiveFields = {1, 2, 3, 4}, .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}}, + // DebugFailingElapsedClock. {{.atomTag = android::util::DEBUG_FAILING_ELAPSED_CLOCK}, {.additiveFields = {1, 2, 3, 4}, .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}}, + // BuildInformation. {{.atomTag = android::util::BUILD_INFORMATION}, {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}}, + // RoleHolder. {{.atomTag = android::util::ROLE_HOLDER}, {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}}, + // PermissionState. {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE}, {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}}, + // TrainInfo. {{.atomTag = android::util::TRAIN_INFO}, {.puller = new TrainInfoPuller()}}, + // TimeZoneDataInfo. {{.atomTag = android::util::TIME_ZONE_DATA_INFO}, {.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}}, + // ExternalStorageInfo {{.atomTag = android::util::EXTERNAL_STORAGE_INFO}, {.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}}, + // GpuStatsGlobalInfo {{.atomTag = android::util::GPU_STATS_GLOBAL_INFO}, {.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}}, + // GpuStatsAppInfo {{.atomTag = android::util::GPU_STATS_APP_INFO}, {.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}}, + // AppsOnExternalStorageInfo {{.atomTag = android::util::APPS_ON_EXTERNAL_STORAGE_INFO}, {.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}}, + // Face Settings {{.atomTag = android::util::FACE_SETTINGS}, {.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}}, + // App ops {{.atomTag = android::util::APP_OPS}, {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}}, + // VmsClientStats {{.atomTag = android::util::VMS_CLIENT_STATS}, {.additiveFields = {5, 6, 7, 8, 9, 10}, .puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}}, + // NotiifcationRemoteViews. {{.atomTag = android::util::NOTIFICATION_REMOTE_VIEWS}, {.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}}, + // PermissionStateSampled. {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE_SAMPLED}, {.puller = diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp index ef59c9242cb2..3ad21e0c96ae 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -28,10 +28,14 @@ namespace statsd { StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo) : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) { // create matcher for each primary field - // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain - for (const auto& primary : stateAtomInfo.primaryFields) { - Matcher matcher = getSimpleMatcher(atomId, primary); - mPrimaryFields.push_back(matcher); + for (const auto& primaryField : stateAtomInfo.primaryFields) { + if (primaryField == util::FIRST_UID_IN_CHAIN) { + Matcher matcher = getFirstUidMatcher(atomId); + mPrimaryFields.push_back(matcher); + } else { + Matcher matcher = getSimpleMatcher(atomId, primaryField); + mPrimaryFields.push_back(matcher); + } } // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h index 7453370f25fd..70f16274c7f6 100644 --- a/cmds/statsd/src/state/StateTracker.h +++ b/cmds/statsd/src/state/StateTracker.h @@ -72,7 +72,7 @@ private: int32_t mDefaultState = kStateUnknown; - int32_t mResetState; + int32_t mResetState = kStateUnknown; // Maps primary key to state value info std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap; diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index 26a3733ed598..84aaa54bc5bf 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -76,6 +76,23 @@ std::shared_ptr<LogEvent> buildUidProcessEvent(int uid, int state) { return event; } +// State with first uid in attribution chain as primary field - WakelockStateChanged +std::shared_ptr<LogEvent> buildPartialWakelockEvent(int uid, const std::string& tag, bool acquire) { + std::vector<AttributionNodeInternal> chain; + chain.push_back(AttributionNodeInternal()); + AttributionNodeInternal& attr = chain.back(); + attr.set_uid(uid); + + std::shared_ptr<LogEvent> event = + std::make_shared<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, 1000 /* timestamp */); + event->write(chain); + event->write((int32_t)1); // PARTIAL_WAKE_LOCK + event->write(tag); + event->write(acquire ? 1 : 0); + event->init(); + return event; +} + // State with multiple primary fields - OverlayStateChanged std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) { std::shared_ptr<LogEvent> event = @@ -134,6 +151,39 @@ void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { key->addValue(FieldValue(field1, value1)); key->addValue(FieldValue(field2, value2)); } + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + int pos4[] = {3, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + + Field field3(10 /* atom id */, pos3, 0 /* depth */); + Field field4(10 /* atom id */, pos4, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + Value value4(tag); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); + key->addValue(FieldValue(field4, value4)); +} + +void getPartialWakelockKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + Field field3(10 /* atom id */, pos3, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); +} // END: get primary key functions TEST(StateListenerTest, TestStateListenerWeakPointer) { @@ -247,7 +297,8 @@ TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey)); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey)); } /** @@ -272,7 +323,46 @@ TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey; getUidProcessKey(1000 /* uid */, &queryKey); - EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey)); +} + +TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener1); + + // Log event. + std::shared_ptr<LogEvent> event = + buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */); + mgr.onLogEvent(*event); + + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(android::util::WAKELOCK_STATE_CHANGED)); + + // Check listener was updated. + EXPECT_EQ(1, listener1->updates.size()); + EXPECT_EQ(3, listener1->updates[0].mKey.getValues().size()); + EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value); + EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value); + EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState); + + // Check StateTracker was updated by querying for state. + HashableDimensionKey queryKey; + getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey)); + + // No state stored for this query key. + HashableDimensionKey queryKey2; + getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); + EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2)); + + // Partial query fails. + HashableDimensionKey queryKey3; + getPartialWakelockKey(1001 /* uid */, &queryKey3); + EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3)); } /** @@ -297,7 +387,8 @@ TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey; getOverlayKey(1000 /* uid */, "package1", &queryKey); - EXPECT_EQ(1, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey)); + EXPECT_EQ(OverlayStateChanged::ENTERED, + getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey)); } /** @@ -326,10 +417,12 @@ TEST(StateTrackerTest, TestStateQuery) { sp<TestStateListener> listener1 = new TestStateListener(); sp<TestStateListener> listener2 = new TestStateListener(); sp<TestStateListener> listener3 = new TestStateListener(); + sp<TestStateListener> listener4 = new TestStateListener(); StateManager mgr; mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1); mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2); mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3); + mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener4); std::shared_ptr<LogEvent> event1 = buildUidProcessEvent( 1000, @@ -346,8 +439,12 @@ TEST(StateTrackerTest, TestStateQuery) { android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 std::shared_ptr<LogEvent> event5 = buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON); - std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1); - std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2); + std::shared_ptr<LogEvent> event6 = + buildOverlayEvent(1000, "package1", OverlayStateChanged::ENTERED); + std::shared_ptr<LogEvent> event7 = + buildOverlayEvent(1000, "package2", OverlayStateChanged::EXITED); + std::shared_ptr<LogEvent> event8 = buildPartialWakelockEvent(1005, "tag1", true); + std::shared_ptr<LogEvent> event9 = buildPartialWakelockEvent(1005, "tag2", false); mgr.onLogEvent(*event1); mgr.onLogEvent(*event2); @@ -356,11 +453,14 @@ TEST(StateTrackerTest, TestStateQuery) { mgr.onLogEvent(*event5); mgr.onLogEvent(*event6); mgr.onLogEvent(*event7); + mgr.onLogEvent(*event8); + mgr.onLogEvent(*event9); // Query for UidProcessState of uid 1001 HashableDimensionKey queryKey1; getUidProcessKey(1001, &queryKey1); - EXPECT_EQ(1003, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); // Query for UidProcessState of uid 1004 - not in state map HashableDimensionKey queryKey2; @@ -370,15 +470,30 @@ TEST(StateTrackerTest, TestStateQuery) { // Query for UidProcessState of uid 1001 - after change in state mgr.onLogEvent(*event4); - EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); // Query for ScreenState - EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); // Query for OverlayState of uid 1000, package name "package2" HashableDimensionKey queryKey3; getOverlayKey(1000, "package2", &queryKey3); - EXPECT_EQ(2, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3)); + EXPECT_EQ(OverlayStateChanged::EXITED, + getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3)); + + // Query for WakelockState of uid 1005, tag 2 + HashableDimensionKey queryKey4; + getPartialWakelockKey(1005, "tag2", &queryKey4); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey4)); + + // Query for WakelockState of uid 1005, tag 1 + HashableDimensionKey queryKey5; + getPartialWakelockKey(1005, "tag1", &queryKey5); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey5)); } } // namespace statsd diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt index 4d0acb3c2330..4aaf72728cbf 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -22519,8 +22519,6 @@ HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhone(Landroid/cont HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState; -HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V -HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object; HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo; HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String; @@ -37729,7 +37727,6 @@ Lcom/android/internal/telephony/PhoneFactory; Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState; Lcom/android/internal/telephony/PhoneInternalInterface; Lcom/android/internal/telephony/PhoneNotifier; -Lcom/android/internal/telephony/PhoneStateIntentReceiver; Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper; Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper; Lcom/android/internal/telephony/PhoneSubInfoController; diff --git a/config/preloaded-classes b/config/preloaded-classes index 97f009c09217..e53c74bdfc4b 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -4841,7 +4841,6 @@ com.android.internal.telephony.PhoneConstants$State com.android.internal.telephony.PhoneFactory com.android.internal.telephony.PhoneInternalInterface com.android.internal.telephony.PhoneNotifier -com.android.internal.telephony.PhoneStateIntentReceiver com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper com.android.internal.telephony.PhoneSubInfoController diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index c0fee6e9e5e9..e46840c0f467 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1547,6 +1547,34 @@ public abstract class AccessibilityService extends Service { void onShowModeChanged(@NonNull SoftKeyboardController controller, @SoftKeyboardShowMode int showMode); } + + /** + * Switches the current IME for the user for whom the service is enabled. The change will + * persist until the current IME is explicitly changed again, and may persist beyond the + * life cycle of the requesting service. + * + * @param imeId The ID of the input method to make current. This IME must be installed and + * enabled. + * @return {@code true} if the current input method was successfully switched to the input + * method by {@code imeId}, + * {@code false} if the input method specified is not installed, not enabled, or + * otherwise not available to become the current IME + * + * @see android.view.inputmethod.InputMethodInfo#getId() + */ + public boolean switchToInputMethod(@NonNull String imeId) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.switchToInputMethod(imeId); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + return false; + } } /** diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 4841781170e1..656f87fe435b 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -91,6 +91,8 @@ interface IAccessibilityServiceConnection { void setSoftKeyboardCallbackEnabled(boolean enabled); + boolean switchToInputMethod(String imeId); + boolean isAccessibilityButtonAvailable(); void sendGesture(int sequence, in ParceledListSlice gestureSteps); diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 8fe2f12a1023..f2702a864c2f 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -25,6 +25,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.Size; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.UserHandleAware; import android.app.Activity; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; @@ -40,6 +41,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; @@ -528,12 +530,9 @@ public class AccountManager { * authenticator known to the AccountManager service. Empty (never * null) if no authenticators are known. */ + @UserHandleAware public AuthenticatorDescription[] getAuthenticatorTypes() { - try { - return mService.getAuthenticatorTypes(UserHandle.getCallingUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getAuthenticatorTypesAsUser(mContext.getUserId()); } /** @@ -584,13 +583,10 @@ public class AccountManager { * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts * have been added. */ + @UserHandleAware @NonNull public Account[] getAccounts() { - try { - return mService.getAccounts(null, mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getAccountsAsUser(mContext.getUserId()); } /** @@ -708,6 +704,7 @@ public class AccountManager { * @return An array of {@link Account}, one per matching account. Empty (never null) if no * accounts of the specified type have been added. */ + @UserHandleAware @NonNull public Account[] getAccountsByType(String type) { return getAccountsByTypeAsUser(type, mContext.getUser()); @@ -1183,23 +1180,11 @@ public class AccountManager { * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)} * instead */ + @UserHandleAware @Deprecated public AccountManagerFuture<Boolean> removeAccount(final Account account, AccountManagerCallback<Boolean> callback, Handler handler) { - if (account == null) throw new IllegalArgumentException("account is null"); - return new Future2Task<Boolean>(handler, callback) { - @Override - public void doWork() throws RemoteException { - mService.removeAccount(mResponse, account, false); - } - @Override - public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { - if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { - throw new AuthenticatorException("no result in response"); - } - return bundle.getBoolean(KEY_BOOLEAN_RESULT); - } - }.start(); + return removeAccountAsUser(account, callback, handler, mContext.getUser()); } /** @@ -1243,15 +1228,10 @@ public class AccountManager { * adding accounts (of this type) has been disabled by policy * </ul> */ + @UserHandleAware public AccountManagerFuture<Bundle> removeAccount(final Account account, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { - if (account == null) throw new IllegalArgumentException("account is null"); - return new AmsTask(activity, handler, callback) { - @Override - public void doWork() throws RemoteException { - mService.removeAccount(mResponse, account, activity != null); - } - }.start(); + return removeAccountAsUser(account, activity, callback, handler, mContext.getUser()); } /** @@ -1841,24 +1821,30 @@ public class AccountManager { * creating a new account, usually because of network trouble * </ul> */ + @UserHandleAware public AccountManagerFuture<Bundle> addAccount(final String accountType, final String authTokenType, final String[] requiredFeatures, final Bundle addAccountOptions, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { - if (accountType == null) throw new IllegalArgumentException("accountType is null"); - final Bundle optionsIn = new Bundle(); - if (addAccountOptions != null) { - optionsIn.putAll(addAccountOptions); - } - optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); - - return new AmsTask(activity, handler, callback) { - @Override - public void doWork() throws RemoteException { - mService.addAccount(mResponse, accountType, authTokenType, - requiredFeatures, activity != null, optionsIn); + if (Process.myUserHandle().equals(mContext.getUser())) { + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + final Bundle optionsIn = new Bundle(); + if (addAccountOptions != null) { + optionsIn.putAll(addAccountOptions); } - }.start(); + optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); + + return new AmsTask(activity, handler, callback) { + @Override + public void doWork() throws RemoteException { + mService.addAccount(mResponse, accountType, authTokenType, + requiredFeatures, activity != null, optionsIn); + } + }.start(); + } else { + return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions, + activity, callback, handler, mContext.getUser()); + } } /** @@ -2002,6 +1988,7 @@ public class AccountManager { * verifying the password, usually because of network trouble * </ul> */ + @UserHandleAware public AccountManagerFuture<Bundle> confirmCredentials(final Account account, final Bundle options, final Activity activity, @@ -3209,6 +3196,7 @@ public class AccountManager { * </ul> * @see #startAddAccountSession and #startUpdateCredentialsSession */ + @UserHandleAware public AccountManagerFuture<Bundle> finishSession( final Bundle sessionBundle, final Activity activity, diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 012713891d11..ce68e082cf4f 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -34,7 +34,6 @@ interface IAccountManager { String getPassword(in Account account); String getUserData(in Account account, String key); AuthenticatorDescription[] getAuthenticatorTypes(int userId); - Account[] getAccounts(String accountType, String opPackageName); Account[] getAccountsForPackage(String packageName, int uid, String opPackageName); Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName); Account[] getAccountsAsUser(String accountType, int userId, String opPackageName); @@ -45,8 +44,6 @@ interface IAccountManager { void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features, String opPackageName); boolean addAccountExplicitly(in Account account, String password, in Bundle extras); - void removeAccount(in IAccountManagerResponse response, in Account account, - boolean expectActivityLaunch); void removeAccountAsUser(in IAccountManagerResponse response, in Account account, boolean expectActivityLaunch, int userId); boolean removeAccountExplicitly(in Account account); diff --git a/core/java/android/annotation/SystemService.java b/core/java/android/annotation/SystemService.java index 0c5d15e178a3..c05c1bab06d2 100644 --- a/core/java/android/annotation/SystemService.java +++ b/core/java/android/annotation/SystemService.java @@ -19,14 +19,12 @@ package android.annotation; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.SOURCE; -import android.content.Context; - import java.lang.annotation.Retention; import java.lang.annotation.Target; /** * Description of a system service available through - * {@link Context#getSystemService(Class)}. This is used to auto-generate + * {@link android.content.Context#getSystemService(Class)}. This is used to auto-generate * documentation explaining how to obtain a reference to the service. * * @hide @@ -36,9 +34,9 @@ import java.lang.annotation.Target; public @interface SystemService { /** * The string name of the system service that can be passed to - * {@link Context#getSystemService(String)}. + * {@link android.content.Context#getSystemService(String)}. * - * @see Context#getSystemServiceName(Class) + * @see android.content.Context#getSystemServiceName(Class) */ String value(); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9aef20b29490..c3b07c8644da 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4073,6 +4073,29 @@ public class ActivityManager { } /** + * Updates mcc mnc configuration and applies changes to the entire system. + * + * @param mcc mcc configuration to update. + * @param mnc mnc configuration to update. + * @throws RemoteException; IllegalArgumentException if mcc or mnc is null; + * @return Returns {@code true} if the configuration was updated successfully; + * {@code false} otherwise. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) + public boolean updateMccMncConfiguration(@NonNull String mcc, @NonNull String mnc) { + if (mcc == null || mnc == null) { + throw new IllegalArgumentException("mcc or mnc cannot be null."); + } + try { + return getService().updateMccMncConfiguration(mcc, mnc); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Logs out current current foreground user by switching to the system user and stopping the * user being switched from. * @hide diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 032e8245ca42..4f3e8ec9fb51 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -45,8 +45,19 @@ public abstract class ActivityManagerInternal { // Access modes for handleIncomingUser. public static final int ALLOW_NON_FULL = 0; + /** + * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} + * if in the same profile group. + * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. + */ public static final int ALLOW_NON_FULL_IN_PROFILE = 1; public static final int ALLOW_FULL_ONLY = 2; + /** + * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} + * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group. + * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. + */ + public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3; /** * Verify that calling app has access to the given provider. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4a8e4e2ae012..a11f41fbc5d0 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -726,7 +726,17 @@ public class AppOpsManager { /** @hide Capture the device's display contents and/or audio */ @UnsupportedAppUsage public static final int OP_PROJECT_MEDIA = 46; - /** @hide Activate a VPN connection without user intervention. */ + /** + * Start (without additional user intervention) a VPN connection, as used by {@link + * android.net.VpnService} along with as Platform VPN connections, as used by {@link + * android.net.VpnManager} + * + * <p>This appop is granted to apps that have already been given user consent to start + * VpnService based VPN connections. As this is a superset of OP_ACTIVATE_PLATFORM_VPN, this + * appop also allows the starting of Platform VPNs. + * + * @hide + */ @UnsupportedAppUsage public static final int OP_ACTIVATE_VPN = 47; /** @hide Access the WallpaperManagerAPI to write wallpapers. */ @@ -852,10 +862,21 @@ public class AppOpsManager { public static final int OP_MANAGE_EXTERNAL_STORAGE = 92; /** @hide Communicate cross-profile within the same profile group. */ public static final int OP_INTERACT_ACROSS_PROFILES = 93; + /** + * Start (without additional user intervention) a Platform VPN connection, as used by {@link + * android.net.VpnManager} + * + * <p>This appop is granted to apps that have already been given user consent to start Platform + * VPN connections. This appop is insufficient to start VpnService based VPNs; OP_ACTIVATE_VPN + * is needed for that. + * + * @hide + */ + public static final int OP_ACTIVATE_PLATFORM_VPN = 94; /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 94; + public static final int _NUM_OP = 95; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1149,6 +1170,8 @@ public class AppOpsManager { /** @hide Communicate cross-profile within the same profile group. */ @SystemApi public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles"; + /** @hide Start Platform VPN without user intervention */ + public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn"; /** {@link #sAppOpsToNote} not initialized yet for this op */ @@ -1333,6 +1356,7 @@ public class AppOpsManager { OP_QUERY_ALL_PACKAGES, // QUERY_ALL_PACKAGES OP_MANAGE_EXTERNAL_STORAGE, // MANAGE_EXTERNAL_STORAGE OP_INTERACT_ACROSS_PROFILES, //INTERACT_ACROSS_PROFILES + OP_ACTIVATE_PLATFORM_VPN, // ACTIVATE_PLATFORM_VPN }; /** @@ -1433,6 +1457,7 @@ public class AppOpsManager { OPSTR_QUERY_ALL_PACKAGES, OPSTR_MANAGE_EXTERNAL_STORAGE, OPSTR_INTERACT_ACROSS_PROFILES, + OPSTR_ACTIVATE_PLATFORM_VPN, }; /** @@ -1533,7 +1558,8 @@ public class AppOpsManager { "ACCESS_MEDIA_LOCATION", "QUERY_ALL_PACKAGES", "MANAGE_EXTERNAL_STORAGE", - "INTERACT_ACROSS_PROFILES" + "INTERACT_ACROSS_PROFILES", + "ACTIVATE_PLATFORM_VPN", }; /** @@ -1636,6 +1662,7 @@ public class AppOpsManager { null, // no permission for OP_QUERY_ALL_PACKAGES Manifest.permission.MANAGE_EXTERNAL_STORAGE, android.Manifest.permission.INTERACT_ACROSS_PROFILES, + null, // no permission for OP_ACTIVATE_PLATFORM_VPN }; /** @@ -1738,6 +1765,7 @@ public class AppOpsManager { null, // QUERY_ALL_PACKAGES null, // MANAGE_EXTERNAL_STORAGE null, // INTERACT_ACROSS_PROFILES + null, // ACTIVATE_PLATFORM_VPN }; /** @@ -1839,6 +1867,7 @@ public class AppOpsManager { false, // QUERY_ALL_PACKAGES false, // MANAGE_EXTERNAL_STORAGE false, // INTERACT_ACROSS_PROFILES + false, // ACTIVATE_PLATFORM_VPN }; /** @@ -1939,6 +1968,7 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES + AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN }; /** @@ -2043,6 +2073,7 @@ public class AppOpsManager { false, // QUERY_ALL_PACKAGES false, // MANAGE_EXTERNAL_STORAGE false, // INTERACT_ACROSS_PROFILES + false, // ACTIVATE_PLATFORM_VPN }; /** diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java index d993ec17eeab..3febf7166837 100644 --- a/core/java/android/app/AsyncNotedAppOp.java +++ b/core/java/android/app/AsyncNotedAppOp.java @@ -256,10 +256,10 @@ public final class AsyncNotedAppOp implements Parcelable { }; @DataClass.Generated( - time = 1578321462996L, + time = 1578516519372L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java", - inputSignatures = "private final @android.annotation.IntRange(from=0L, to=93L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)") + inputSignatures = "private final @android.annotation.IntRange(from=0L, to=94L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e8494c4c5893..bd6baec30be0 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -188,6 +188,16 @@ interface IActivityManager { */ @UnsupportedAppUsage boolean updateConfiguration(in Configuration values); + /** + * Updates mcc mnc configuration and applies changes to the entire system. + * + * @param mcc mcc configuration to update. + * @param mnc mnc configuration to update. + * @throws RemoteException; IllegalArgumentException if mcc or mnc is null. + * @return Returns {@code true} if the configuration was updated; + * {@code false} otherwise. + */ + boolean updateMccMncConfiguration(in String mcc, in String mnc); boolean stopServiceToken(in ComponentName className, in IBinder token, int startId); @UnsupportedAppUsage void setProcessLimit(int max); diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 700b3c1b620e..e5c046c2376c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -68,6 +68,7 @@ import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; import android.view.IRecentsAnimationRunner; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.view.WindowContainerTransaction; @@ -121,6 +122,9 @@ interface IActivityTaskManager { in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, IBinder permissionToken, boolean ignoreTargetSecurity, int userId); + + void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode); + boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType, int userId); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 86f52af1a13b..fcdb7cc0855d 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -94,8 +94,11 @@ interface INotificationManager void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group); void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel); NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId); + NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId); + void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId); NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted); void deleteNotificationChannel(String pkg, String channelId); + void deleteConversationNotificationChannels(String pkg, int uid, String conversationId); ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId); ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 3eee1ae5c00c..a33c2c19d05c 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.media.AudioAttributes; import android.net.Uri; import android.os.Parcel; @@ -57,6 +58,22 @@ public final class NotificationChannel implements Parcelable { public static final String DEFAULT_CHANNEL_ID = "miscellaneous"; /** + * The formatter used by the system to create an id for notification + * channels when it automatically creates conversation channels on behalf of an app. The format + * string takes two arguments, in this order: the + * {@link #getId()} of the original notification channel, and the + * {@link ShortcutInfo#getId() id} of the conversation. + */ + public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s"; + + /** + * TODO: STOPSHIP remove + * Conversation id to use for apps that aren't providing them yet. + * @hide + */ + public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id"; + + /** * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this * limit. */ @@ -85,6 +102,8 @@ public final class NotificationChannel implements Parcelable { private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system"; private static final String ATT_ALLOW_BUBBLE = "can_bubble"; private static final String ATT_ORIG_IMP = "orig_imp"; + private static final String ATT_PARENT_CHANNEL = "parent"; + private static final String ATT_CONVERSATION_ID = "conv_id"; private static final String DELIMITER = ","; /** @@ -147,7 +166,7 @@ public final class NotificationChannel implements Parcelable { private static final boolean DEFAULT_ALLOW_BUBBLE = true; @UnsupportedAppUsage - private final String mId; + private String mId; private String mName; private String mDesc; private int mImportance = DEFAULT_IMPORTANCE; @@ -172,6 +191,8 @@ public final class NotificationChannel implements Parcelable { private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE; private boolean mImportanceLockedByOEM; private boolean mImportanceLockedDefaultApp; + private String mParentId = null; + private String mConversationId = null; /** * Creates a notification channel. @@ -236,6 +257,8 @@ public final class NotificationChannel implements Parcelable { mAllowBubbles = in.readBoolean(); mImportanceLockedByOEM = in.readBoolean(); mOriginalImportance = in.readInt(); + mParentId = in.readString(); + mConversationId = in.readString(); } @Override @@ -291,6 +314,8 @@ public final class NotificationChannel implements Parcelable { dest.writeBoolean(mAllowBubbles); dest.writeBoolean(mImportanceLockedByOEM); dest.writeInt(mOriginalImportance); + dest.writeString(mParentId); + dest.writeString(mConversationId); } /** @@ -363,6 +388,13 @@ public final class NotificationChannel implements Parcelable { // Modifiable by apps on channel creation. /** + * @hide + */ + public void setId(String id) { + mId = id; + } + + /** * Sets what group this channel belongs to. * * Group information is only used for presentation, not for behavior. @@ -502,6 +534,23 @@ public final class NotificationChannel implements Parcelable { } /** + * Sets this channel as being person-centric. Different settings and functionality may be + * exposed for people-centric channels. + * + * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of + * this type would be posted to in absence of a specific conversation id. + * For example, if this channel represents 'Messages from Person A', the + * parent channel would be 'Messages.' + * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this + * channel's conversation. + */ + public void setConversationId(@Nullable String parentChannelId, + @Nullable String conversationId) { + mParentId = parentChannelId; + mConversationId = conversationId; + } + + /** * Returns the id of this channel. */ public String getId() { @@ -622,6 +671,22 @@ public final class NotificationChannel implements Parcelable { } /** + * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's + * a conversation related channel. See {@link #setConversationId(String, String)}. + */ + public @Nullable String getParentChannelId() { + return mParentId; + } + + /** + * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's + * associated with a conversation. See {@link #setConversationId(String, String)}. + */ + public @Nullable String getConversationId() { + return mConversationId; + } + + /** * @hide */ @SystemApi @@ -761,6 +826,8 @@ public final class NotificationChannel implements Parcelable { setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false)); setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE)); + setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null), + parser.getAttributeValue(ATT_CONVERSATION_ID, null)); } @Nullable @@ -885,6 +952,12 @@ public final class NotificationChannel implements Parcelable { if (getOriginalImportance() != DEFAULT_IMPORTANCE) { out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance())); } + if (getParentChannelId() != null) { + out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId()); + } + if (getConversationId() != null) { + out.attribute(null, ATT_CONVERSATION_ID, getConversationId()); + } // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of // truth and so aren't written to this xml file @@ -1042,7 +1115,9 @@ public final class NotificationChannel implements Parcelable { && Objects.equals(getAudioAttributes(), that.getAudioAttributes()) && mImportanceLockedByOEM == that.mImportanceLockedByOEM && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp - && mOriginalImportance == that.mOriginalImportance; + && mOriginalImportance == that.mOriginalImportance + && Objects.equals(getParentChannelId(), that.getParentChannelId()) + && Objects.equals(getConversationId(), that.getConversationId()); } @Override @@ -1052,7 +1127,8 @@ public final class NotificationChannel implements Parcelable { getUserLockedFields(), isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(), getAudioAttributes(), isBlockableSystem(), mAllowBubbles, - mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance); + mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance, + mParentId, mConversationId); result = 31 * result + Arrays.hashCode(mVibration); return result; } @@ -1063,26 +1139,7 @@ public final class NotificationChannel implements Parcelable { String output = "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + redactedName - + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") - + ", mImportance=" + mImportance - + ", mBypassDnd=" + mBypassDnd - + ", mLockscreenVisibility=" + mLockscreenVisibility - + ", mSound=" + mSound - + ", mLights=" + mLights - + ", mLightColor=" + mLightColor - + ", mVibration=" + Arrays.toString(mVibration) - + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields) - + ", mFgServiceShown=" + mFgServiceShown - + ", mVibrationEnabled=" + mVibrationEnabled - + ", mShowBadge=" + mShowBadge - + ", mDeleted=" + mDeleted - + ", mGroup='" + mGroup + '\'' - + ", mAudioAttributes=" + mAudioAttributes - + ", mBlockableSystem=" + mBlockableSystem - + ", mAllowBubbles=" + mAllowBubbles - + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM - + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp - + ", mOriginalImp=" + mOriginalImportance + + getFieldsString() + '}'; pw.println(prefix + output); } @@ -1092,7 +1149,12 @@ public final class NotificationChannel implements Parcelable { return "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + mName - + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + + getFieldsString() + + '}'; + } + + private String getFieldsString() { + return ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + ", mImportance=" + mImportance + ", mBypassDnd=" + mBypassDnd + ", mLockscreenVisibility=" + mLockscreenVisibility @@ -1112,7 +1174,8 @@ public final class NotificationChannel implements Parcelable { + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + ", mOriginalImp=" + mOriginalImportance - + '}'; + + ", mParent=" + mParentId + + ", mConversationId" + mConversationId; } /** @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index fdbb8bb0d4c5..61c109885e05 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -843,6 +843,25 @@ public class NotificationManager { } /** + * Returns the notification channel settings for a given channel and conversation id. + * + * <p>The channel must belong to your package, or to a package you are an approved notification + * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query + * a channel as a notification delegate, call this method from a context created for that + * package (see {@link Context#createPackageContext(String, int)}).</p> + */ + public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId, + @NonNull String conversationId) { + INotificationManager service = getService(); + try { + return service.getConversationNotificationChannel(mContext.getOpPackageName(), + mContext.getUserId(), mContext.getPackageName(), channelId, conversationId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns all notification channels belonging to the calling package. * * <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 844e72ecf07c..736efb6a5e69 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -15,6 +15,7 @@ */ package android.app; + import android.annotation.NonNull; import android.os.SystemProperties; import android.util.Log; @@ -23,6 +24,7 @@ import com.android.internal.annotations.GuardedBy; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; @@ -164,6 +166,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { private static final String TAG = "PropertyInvalidatedCache"; private static final boolean DEBUG = false; private static final boolean ENABLE = true; + private static final boolean VERIFY = false; private final Object mLock = new Object(); @@ -228,6 +231,18 @@ public abstract class PropertyInvalidatedCache<Query, Result> { protected abstract Result recompute(Query query); /** + * Determines if a pair of responses are considered equal. Used to determine whether + * a cache is inadvertently returning stale results when VERIFY is set to true. + */ + protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) { + // If a service crashes and returns a null result, the cached value remains valid. + if (fetchedResult != null) { + return Objects.equals(cachedResult, fetchedResult); + } + return true; + } + + /** * Make result up-to-date on a cache hit. Called unlocked; * may block. * @@ -334,12 +349,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> { mCache.put(query, refreshedResult); } } - return refreshedResult; + return maybeCheckConsistency(query, refreshedResult); } if (DEBUG) { Log.d(TAG, "cache hit for " + query); } - return cachedResult; + return maybeCheckConsistency(query, cachedResult); } // Cache miss: make the value from scratch. if (DEBUG) { @@ -353,7 +368,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { mCache.put(query, result); } } - return result; + return maybeCheckConsistency(query, result); } } @@ -425,4 +440,15 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } SystemProperties.set(name, newValueString); } + + private Result maybeCheckConsistency(Query query, Result proposedResult) { + if (VERIFY) { + Result resultToCompare = recompute(query); + boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce); + if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) { + throw new AssertionError("cache returned out of date response for " + query); + } + } + return proposedResult; + } } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index aa115984b5e1..d23754e9d433 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -482,19 +482,6 @@ public class ResourcesManager { } } - if (key.mOverlayDirs != null) { - for (final String idmapPath : key.mOverlayDirs) { - try { - builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, - true /*overlay*/)); - } catch (IOException e) { - Log.w(TAG, "failed to add overlay path " + idmapPath); - - // continue. - } - } - } - if (key.mLibDirs != null) { for (final String libDir : key.mLibDirs) { if (libDir.endsWith(".apk")) { @@ -513,6 +500,19 @@ public class ResourcesManager { } } + if (key.mOverlayDirs != null) { + for (final String idmapPath : key.mOverlayDirs) { + try { + builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, + true /*overlay*/)); + } catch (IOException e) { + Log.w(TAG, "failed to add overlay path " + idmapPath); + + // continue. + } + } + } + return builder.build(); } diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index 84263749232d..dde6dda8e448 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -26,7 +26,6 @@ import android.content.Context; import android.os.IBinder; import android.os.IPullAtomCallback; import android.os.IPullAtomResultReceiver; -import android.os.IStatsCompanionService; import android.os.IStatsManagerService; import android.os.IStatsPullerCallback; import android.os.IStatsd; @@ -61,9 +60,6 @@ public final class StatsManager { private IStatsd mService; @GuardedBy("sLock") - private IStatsCompanionService mStatsCompanion; - - @GuardedBy("sLock") private IStatsManagerService mStatsManagerService; /** @@ -538,7 +534,7 @@ public final class StatsManager { } synchronized (sLock) { try { - IStatsCompanionService service = getIStatsCompanionServiceLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); PullAtomCallbackInternal rec = new PullAtomCallbackInternal(atomTag, callback, executor); service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields, @@ -560,7 +556,7 @@ public final class StatsManager { public void unregisterPullAtomCallback(int atomTag) { synchronized (sLock) { try { - IStatsCompanionService service = getIStatsCompanionServiceLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); service.unregisterPullAtomCallback(atomTag); } catch (RemoteException e) { throw new RuntimeException("Unable to unregister pull atom callback"); @@ -746,16 +742,6 @@ public final class StatsManager { } @GuardedBy("sLock") - private IStatsCompanionService getIStatsCompanionServiceLocked() { - if (mStatsCompanion != null) { - return mStatsCompanion; - } - mStatsCompanion = IStatsCompanionService.Stub.asInterface( - ServiceManager.getService("statscompanion")); - return mStatsCompanion; - } - - @GuardedBy("sLock") private IStatsManagerService getIStatsManagerServiceLocked() { if (mStatsManagerService != null) { return mStatsManagerService; diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 078e4538c66b..42563b5a1561 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -267,6 +267,7 @@ public class StatusBarManager { * @hide */ @UnsupportedAppUsage + @TestApi public void expandNotificationsPanel() { try { final IStatusBarService svc = getService(); @@ -284,6 +285,7 @@ public class StatusBarManager { * @hide */ @UnsupportedAppUsage + @TestApi public void collapsePanels() { try { final IStatusBarService svc = getService(); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index ca3d0d7065c9..a9be9ea33089 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -170,6 +170,7 @@ import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; import android.service.vr.IVrManager; import android.telecom.TelecomManager; +import android.telephony.MmsManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyRegistryManager; import android.util.ArrayMap; @@ -345,6 +346,14 @@ public final class SystemServiceRegistry { } }); + registerService(Context.NETWORK_STACK_SERVICE, IBinder.class, + new StaticServiceFetcher<IBinder>() { + @Override + public IBinder createService() { + return ServiceManager.getService(Context.NETWORK_STACK_SERVICE); + } + }); + registerService(Context.TETHERING_SERVICE, TetheringManager.class, new CachedServiceFetcher<TetheringManager>() { @Override @@ -631,6 +640,13 @@ public final class SystemServiceRegistry { return new TelecomManager(ctx.getOuterContext()); }}); + registerService(Context.MMS_SERVICE, MmsManager.class, + new CachedServiceFetcher<MmsManager>() { + @Override + public MmsManager createService(ContextImpl ctx) { + return new MmsManager(ctx.getOuterContext()); + }}); + registerService(Context.UI_MODE_SERVICE, UiModeManager.class, new CachedServiceFetcher<UiModeManager>() { @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 2aac94c6f5da..caaa686ecf08 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -398,6 +398,42 @@ public class DevicePolicyManager { "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; /** + * Activity action: Starts the provisioning flow which sets up a financed device. + * + * <p>During financed device provisioning, a device admin app is downloaded and set as the owner + * of the device. A device owner has full control over the device. The device owner can not be + * modified by the user. + * + * <p>A typical use case would be a device that is bought from the reseller through financing + * program. + * + * <p>An intent with this action can be sent only on an unprovisioned device. + * + * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent + * by a privileged app with the permission + * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}. + * + * <p>The provisioning intent contains the following properties: + * <ul> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> + * </ul> + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @SystemApi + public static final String ACTION_PROVISION_FINANCED_DEVICE = + "android.app.action.PROVISION_FINANCED_DEVICE"; + + /** * Activity action: Starts the provisioning flow which sets up a managed device. * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}. * @@ -864,6 +900,7 @@ public class DevicePolicyManager { * The name is displayed only during provisioning. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -876,6 +913,7 @@ public class DevicePolicyManager { * during provisioning. If the url is not HTTPS, an error will be shown. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -888,6 +926,7 @@ public class DevicePolicyManager { * as the app label of the package. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -912,6 +951,7 @@ public class DevicePolicyManager { * {@link android.content.ClipData} of the intent too. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -1371,6 +1411,16 @@ public class DevicePolicyManager { = "android.app.action.DEVICE_OWNER_CHANGED"; /** + * Broadcast action: sent when the factory reset protection (FRP) policy is changed. + * + * @see #setFactoryResetProtectionPolicy + * @hide + */ + @SystemApi + public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = + "android.app.action.RESET_PROTECTION_POLICY_CHANGED"; + + /** * The ComponentName of the administrator component. * * @see #ACTION_ADD_DEVICE_ADMIN @@ -1942,16 +1992,6 @@ public class DevicePolicyManager { public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; /** - * Result code for {@link #checkProvisioningPreCondition}. - * - * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is - * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}. - * - * @hide - */ - public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15; - - /** * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre * conditions. * @@ -1963,7 +2003,7 @@ public class DevicePolicyManager { CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED, CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE, CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED, - CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED + CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER }) public @interface ProvisioningPreCondition {} @@ -4289,6 +4329,60 @@ public class DevicePolicyManager { } /** + * Callable by device owner or profile owner of an organization-owned device, to set a + * factory reset protection (FRP) policy. When a new policy is set, the system + * notifies the FRP management agent of a policy change by broadcasting + * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param policy the new FRP policy, or {@code null} to clear the current policy. + * @throws SecurityException if {@code admin} is not a device owner or a profile owner of + * an organization-owned device. + * @throws UnsupportedOperationException if factory reset protection is not + * supported on the device. + */ + public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin, + @Nullable FactoryResetProtectionPolicy policy) { + throwIfParentInstance("setFactoryResetProtectionPolicy"); + if (mService != null) { + try { + mService.setFactoryResetProtectionPolicy(admin, policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Callable by device owner or profile owner of an organization-owned device, to retrieve + * the current factory reset protection (FRP) policy set previously by + * {@link #setFactoryResetProtectionPolicy}. + * <p> + * This method can also be called by the FRP management agent on device, in which case, + * it can pass {@code null} as the ComponentName. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with or + * {@code null} if called by the FRP management agent on device. + * @return The current FRP policy object or {@code null} if no policy is set. + * @throws SecurityException if {@code admin} is not a device owner, a profile owner of + * an organization-owned device or the FRP management agent. + * @throws UnsupportedOperationException if factory reset protection is not + * supported on the device. + */ + public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy( + @Nullable ComponentName admin) { + throwIfParentInstance("getFactoryResetProtectionPolicy"); + if (mService != null) { + try { + return mService.getFactoryResetProtectionPolicy(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return null; + } + + /** * Called by an application that is administering the device to set the * global proxy and exclusion list. * <p> diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl new file mode 100644 index 000000000000..72e639a76d18 --- /dev/null +++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +parcelable FactoryResetProtectionPolicy; diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java new file mode 100644 index 000000000000..ed7477936f9c --- /dev/null +++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.TEXT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * The factory reset protection policy determines which accounts can unlock a device that + * has gone through untrusted factory reset. + * <p> + * Only a device owner or profile owner of an organization-owned device can set a factory + * reset protection policy for the device by calling the {@code DevicePolicyManager} method + * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName, + * FactoryResetProtectionPolicy)}}. + * + * @see DevicePolicyManager#setFactoryResetProtectionPolicy + * @see DevicePolicyManager#getFactoryResetProtectionPolicy + */ +public final class FactoryResetProtectionPolicy implements Parcelable { + + private static final String LOG_TAG = "FactoryResetProtectionPolicy"; + + private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT = + "factory_reset_protection_account"; + private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED = + "factory_reset_protection_disabled"; + private static final String ATTR_VALUE = "value"; + + private final List<String> mFactoryResetProtectionAccounts; + private final boolean mFactoryResetProtectionDisabled; + + private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts, + boolean factoryResetProtectionDisabled) { + mFactoryResetProtectionAccounts = factoryResetProtectionAccounts; + mFactoryResetProtectionDisabled = factoryResetProtectionDisabled; + } + + /** + * Get the list of accounts that can provision a device which has been factory reset. + */ + public @NonNull List<String> getFactoryResetProtectionAccounts() { + return mFactoryResetProtectionAccounts; + } + + /** + * Return whether factory reset protection for the device is disabled or not. + */ + public boolean isFactoryResetProtectionDisabled() { + return mFactoryResetProtectionDisabled; + } + + /** + * Builder class for {@link FactoryResetProtectionPolicy} objects. + */ + public static class Builder { + private List<String> mFactoryResetProtectionAccounts; + private boolean mFactoryResetProtectionDisabled; + + /** + * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}. + */ + public Builder() { + }; + + /** + * Sets which accounts can unlock a device that has been factory reset. + * <p> + * Once set, the consumer unlock flow will be disabled and only accounts in this list + * can unlock factory reset protection after untrusted factory reset. + * <p> + * It's up to the FRP management agent to interpret the {@code String} as account it + * supports. Please consult their relevant documentation for details. + * + * @param factoryResetProtectionAccounts list of accounts. + * @return the same Builder instance. + */ + @NonNull + public Builder setFactoryResetProtectionAccounts( + @NonNull List<String> factoryResetProtectionAccounts) { + mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts); + return this; + } + + /** + * Sets whether factory reset protection is disabled or not. + * <p> + * Once disabled, factory reset protection will not kick in all together when the device + * goes through untrusted factory reset. This applies to both the consumer unlock flow and + * the admin account overrides via {@link #setFactoryResetProtectionAccounts} + * + * @param factoryResetProtectionDisabled Whether the policy is disabled or not. + * @return the same Builder instance. + */ + @NonNull + public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) { + mFactoryResetProtectionDisabled = factoryResetProtectionDisabled; + return this; + } + + /** + * Combines all of the attributes that have been set on this {@code Builder} + * + * @return a new {@link FactoryResetProtectionPolicy} object. + */ + @NonNull + public FactoryResetProtectionPolicy build() { + return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts, + mFactoryResetProtectionDisabled); + } + } + + @Override + public String toString() { + return "FactoryResetProtectionPolicy{" + + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts + + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled + + '}'; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) { + int accountsCount = mFactoryResetProtectionAccounts.size(); + dest.writeInt(accountsCount); + for (String account: mFactoryResetProtectionAccounts) { + dest.writeString(account); + } + dest.writeBoolean(mFactoryResetProtectionDisabled); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR = + new Creator<FactoryResetProtectionPolicy>() { + + @Override + public FactoryResetProtectionPolicy createFromParcel(Parcel in) { + List<String> factoryResetProtectionAccounts = new ArrayList<>(); + int accountsCount = in.readInt(); + for (int i = 0; i < accountsCount; i++) { + factoryResetProtectionAccounts.add(in.readString()); + } + boolean factoryResetProtectionDisabled = in.readBoolean(); + + return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts, + factoryResetProtectionDisabled); + } + + @Override + public FactoryResetProtectionPolicy[] newArray(int size) { + return new FactoryResetProtectionPolicy[size]; + } + }; + + /** + * Restore a previously saved FactoryResetProtectionPolicy from XML. + * <p> + * No validation is required on the reconstructed policy since the XML was previously + * created by the system server from a validated policy. + * @hide + */ + @Nullable + public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) { + try { + boolean factoryResetProtectionDisabled = Boolean.parseBoolean( + parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED)); + + List<String> factoryResetProtectionAccounts = new ArrayList<>(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != END_DOCUMENT + && (type != END_TAG || parser.getDepth() > outerDepth)) { + if (type == END_TAG || type == TEXT) { + continue; + } + if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) { + continue; + } + factoryResetProtectionAccounts.add( + parser.getAttributeValue(null, ATTR_VALUE)); + } + + return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts, + factoryResetProtectionDisabled); + } catch (XmlPullParserException | IOException e) { + Log.w(LOG_TAG, "Reading from xml failed", e); + } + return null; + } + + /** + * @hide + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED, + Boolean.toString(mFactoryResetProtectionDisabled)); + for (String account : mFactoryResetProtectionAccounts) { + out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT); + out.attribute(null, ATTR_VALUE, account); + out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT); + } + } + +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3eec46bd010e..21c9eb5c60ad 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -24,6 +24,7 @@ import android.app.admin.StartInstallingUpdateCallback; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; import android.app.admin.PasswordMetrics; +import android.app.admin.FactoryResetProtectionPolicy; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -104,6 +105,9 @@ interface IDevicePolicyManager { void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent); + void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy); + FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who); + ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList); ComponentName getGlobalProxyAdmin(int userHandle); void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo); diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java index 479e4b4efb4c..bd649f88f40a 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java @@ -18,6 +18,7 @@ package android.app.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.TimestampedValue; @@ -28,17 +29,23 @@ import java.util.List; import java.util.Objects; /** - * A time signal from a telephony source. The value can be {@code null} to indicate that the - * telephony source has entered an "un-opinionated" state and any previously sent suggestions are - * being withdrawn. When not {@code null}, the value consists of the number of milliseconds elapsed - * since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number - * was established. The elapsed realtime clock is considered accurate but volatile, so time signals - * must not be persisted across device resets. + * A time suggestion from an identified telephony source. e.g. from NITZ information from a specific + * radio. + * + * <p>The time value can be {@code null} to indicate that the telephony source has entered an + * "un-opinionated" state and any previous suggestions from the source are being withdrawn. When not + * {@code null}, the value consists of the number of milliseconds elapsed since 1/1/1970 00:00:00 + * UTC and the time according to the elapsed realtime clock when that number was established. The + * elapsed realtime clock is considered accurate but volatile, so time suggestions must not be + * persisted across device resets. * * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class PhoneTimeSuggestion implements Parcelable { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR = new Parcelable.Creator<PhoneTimeSuggestion>() { public PhoneTimeSuggestion createFromParcel(Parcel in) { @@ -85,15 +92,27 @@ public final class PhoneTimeSuggestion implements Parcelable { dest.writeList(mDebugInfo); } + /** + * Returns an identifier for the source of this suggestion. When a device has several "phones", + * i.e. sim slots or equivalent, it is used to identify which one. + */ public int getPhoneId() { return mPhoneId; } + /** + * Returns the suggestion. {@code null} means that the caller is no longer sure what time it + * is. + */ @Nullable public TimestampedValue<Long> getUtcTime() { return mUtcTime; } + /** + * Returns debug metadata for the suggestion. The information is present in {@link #toString()} + * but is not considered for {@link #equals(Object)} and {@link #hashCode()}. + */ @NonNull public List<String> getDebugInfo() { return mDebugInfo == null @@ -105,7 +124,7 @@ public final class PhoneTimeSuggestion implements Parcelable { * information is present in {@link #toString()} but is not considered for * {@link #equals(Object)} and {@link #hashCode()}. */ - public void addDebugInfo(String debugInfo) { + public void addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { mDebugInfo = new ArrayList<>(); } @@ -156,16 +175,19 @@ public final class PhoneTimeSuggestion implements Parcelable { * * @hide */ - public static class Builder { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final class Builder { private final int mPhoneId; - private TimestampedValue<Long> mUtcTime; - private List<String> mDebugInfo; + @Nullable private TimestampedValue<Long> mUtcTime; + @Nullable private List<String> mDebugInfo; + /** Creates a builder with the specified {@code phoneId}. */ public Builder(int phoneId) { mPhoneId = phoneId; } /** Returns the builder for call chaining. */ + @NonNull public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) { if (utcTime != null) { // utcTime can be null, but the value it holds cannot. @@ -177,6 +199,7 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** Returns the builder for call chaining. */ + @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { mDebugInfo = new ArrayList<>(); @@ -186,6 +209,7 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** Returns the {@link PhoneTimeSuggestion}. */ + @NonNull public PhoneTimeSuggestion build() { return new PhoneTimeSuggestion(this); } diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 54dd1bed5361..7c29f017c02b 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -18,6 +18,7 @@ package android.app.timedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; @@ -29,8 +30,11 @@ import android.util.Log; /** * The interface through which system components can send signals to the TimeDetectorService. + * + * <p>This class is marked non-final for mockito. * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_DETECTOR_SERVICE) public class TimeDetector { private static final String TAG = "timedetector.TimeDetector"; @@ -38,6 +42,7 @@ public class TimeDetector { private final ITimeDetectorService mITimeDetectorService; + /** @hide */ public TimeDetector() throws ServiceNotFoundException { mITimeDetectorService = ITimeDetectorService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE)); @@ -62,6 +67,8 @@ public class TimeDetector { /** * Suggests the user's manually entered current time to the detector. + * + * @hide */ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE) public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) { @@ -77,6 +84,8 @@ public class TimeDetector { /** * A shared utility method to create a {@link ManualTimeSuggestion}. + * + * @hide */ public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) { TimestampedValue<Long> utcTime = @@ -88,6 +97,8 @@ public class TimeDetector { /** * Suggests the time according to a network time source like NTP. + * + * @hide */ @RequiresPermission(android.Manifest.permission.SET_TIME) public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java index e8162488394c..d71ffcb9f772 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java @@ -19,6 +19,7 @@ package android.app.timezonedetector; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -30,12 +31,27 @@ import java.util.List; import java.util.Objects; /** - * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information. + * A time zone suggestion from an identified telephony source, e.g. from MCC and NITZ information + * associated with a specific radio. + * + * <p>The time zone ID can be {@code null} to indicate that the telephony source has entered an + * "un-opinionated" state and any previous suggestions from that source are being withdrawn. + * When not {@code null}, the value consists of a suggested time zone ID and metadata that can be + * used to judge quality / certainty of the suggestion. + * + * <p>{@code matchType} must be set to {@link #MATCH_TYPE_NA} when {@code zoneId} is {@code null}, + * and one of the other {@code MATCH_TYPE_} values when it is not {@code null}. + * + * <p>{@code quality} must be set to {@link #QUALITY_NA} when {@code zoneId} is {@code null}, + * and one of the other {@code QUALITY_} values when it is not {@code null}. * * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class PhoneTimeZoneSuggestion implements Parcelable { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public static final Creator<PhoneTimeZoneSuggestion> CREATOR = new Creator<PhoneTimeZoneSuggestion>() { @@ -58,6 +74,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { return new Builder(phoneId).addDebugInfo(debugInfo).build(); } + /** @hide */ @IntDef({ MATCH_TYPE_NA, MATCH_TYPE_NETWORK_COUNTRY_ONLY, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, MATCH_TYPE_EMULATOR_ZONE_ID, MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY }) @Retention(RetentionPolicy.SOURCE) @@ -90,6 +107,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { */ public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; + /** @hide */ @IntDef({ QUALITY_NA, QUALITY_SINGLE_ZONE, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS }) @Retention(RetentionPolicy.SOURCE) @@ -115,7 +133,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * The ID of the phone this suggestion is associated with. For multiple-sim devices this - * helps to establish origin so filtering / stickiness can be implemented. + * helps to establish source so filtering / stickiness can be implemented. */ private final int mPhoneId; @@ -123,6 +141,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * The suggestion. {@code null} means there is no current suggestion and any previous suggestion * should be forgotten. */ + @Nullable private final String mZoneId; /** @@ -139,9 +158,10 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { private final int mQuality; /** - * Free-form debug information about how the signal was derived. Used for debug only, + * Free-form debug information about how the suggestion was derived. Used for debug only, * intentionally not used in equals(), etc. */ + @Nullable private List<String> mDebugInfo; private PhoneTimeZoneSuggestion(Builder builder) { @@ -182,25 +202,47 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { return 0; } + /** + * Returns an identifier for the source of this suggestion. When a device has several "phones", + * i.e. sim slots or equivalent, it is used to identify which one. + */ public int getPhoneId() { return mPhoneId; } + /** + * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that + * the caller is no longer sure what the current time zone is. See + * {@link PhoneTimeZoneSuggestion} for the associated {@code matchType} / {@code quality} rules. + */ @Nullable public String getZoneId() { return mZoneId; } + /** + * Returns information about how the suggestion was determined which could be used to rank + * suggestions when several are available from different sources. See + * {@link PhoneTimeZoneSuggestion} for the associated rules. + */ @MatchType public int getMatchType() { return mMatchType; } + /** + * Returns information about the likelihood of the suggested zone being correct. See + * {@link PhoneTimeZoneSuggestion} for the associated rules. + */ @Quality public int getQuality() { return mQuality; } + /** + * Returns debug metadata for the suggestion. The information is present in {@link #toString()} + * but is not considered for {@link #equals(Object)} and {@link #hashCode()}. + */ @NonNull public List<String> getDebugInfo() { return mDebugInfo == null @@ -267,36 +309,43 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * * @hide */ - public static class Builder { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final class Builder { private final int mPhoneId; - private String mZoneId; + @Nullable private String mZoneId; @MatchType private int mMatchType; @Quality private int mQuality; - private List<String> mDebugInfo; + @Nullable private List<String> mDebugInfo; public Builder(int phoneId) { mPhoneId = phoneId; } - /** Returns the builder for call chaining. */ - public Builder setZoneId(String zoneId) { + /** + * Returns the builder for call chaining. + */ + @NonNull + public Builder setZoneId(@Nullable String zoneId) { mZoneId = zoneId; return this; } /** Returns the builder for call chaining. */ + @NonNull public Builder setMatchType(@MatchType int matchType) { mMatchType = matchType; return this; } /** Returns the builder for call chaining. */ + @NonNull public Builder setQuality(@Quality int quality) { mQuality = quality; return this; } /** Returns the builder for call chaining. */ + @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { mDebugInfo = new ArrayList<>(); @@ -333,6 +382,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } /** Returns the {@link PhoneTimeZoneSuggestion}. */ + @NonNull public PhoneTimeZoneSuggestion build() { validate(); return new PhoneTimeZoneSuggestion(this); diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index e165d8a76caa..5b5f311264e3 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -18,6 +18,7 @@ package android.app.timezonedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; @@ -28,8 +29,10 @@ import android.util.Log; /** * The interface through which system components can send signals to the TimeZoneDetectorService. * + * <p>This class is non-final for mockito. * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) public class TimeZoneDetector { private static final String TAG = "timezonedetector.TimeZoneDetector"; @@ -37,6 +40,7 @@ public class TimeZoneDetector { private final ITimeZoneDetectorService mITimeZoneDetectorService; + /** @hide */ public TimeZoneDetector() throws ServiceNotFoundException { mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE)); @@ -46,7 +50,10 @@ public class TimeZoneDetector { * Suggests the current time zone, determined using telephony signals, to the detector. The * detector may ignore the signal based on system settings, whether better information is * available, and so on. + * + * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { if (DEBUG) { @@ -62,6 +69,8 @@ public class TimeZoneDetector { /** * Suggests the current time zone, determined for the user's manually information, to the * detector. The detector may ignore the signal based on system settings. + * + * @hide */ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE) public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { @@ -77,6 +86,8 @@ public class TimeZoneDetector { /** * A shared utility method to create a {@link ManualTimeZoneSuggestion}. + * + * @hide */ public static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String why) { ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId); diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 4346d9738ec7..9c4a8f4fbe27 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -16,7 +16,10 @@ package android.app.usage; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.usage.NetworkStats.Bucket; @@ -27,6 +30,9 @@ import android.net.DataUsageRequest; import android.net.INetworkStatsService; import android.net.NetworkIdentity; import android.net.NetworkTemplate; +import android.net.netstats.provider.AbstractNetworkStatsProvider; +import android.net.netstats.provider.NetworkStatsProviderCallback; +import android.net.netstats.provider.NetworkStatsProviderWrapper; import android.os.Binder; import android.os.Handler; import android.os.Looper; @@ -519,6 +525,34 @@ public class NetworkStatsManager { private DataUsageRequest request; } + /** + * Registers a custom provider of {@link android.net.NetworkStats} to combine the network + * statistics that cannot be seen by the kernel to system. To unregister, invoke + * {@link NetworkStatsProviderCallback#unregister()}. + * + * @param tag a human readable identifier of the custom network stats provider. + * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to + * be registered to the system. + * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the + * system. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) + @NonNull public NetworkStatsProviderCallback registerNetworkStatsProvider( + @NonNull String tag, + @NonNull AbstractNetworkStatsProvider provider) { + try { + final NetworkStatsProviderWrapper wrapper = new NetworkStatsProviderWrapper(provider); + return new NetworkStatsProviderCallback( + mService.registerNetworkStatsProvider(tag, wrapper)); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + // Unreachable code, but compiler doesn't know about it. + return null; + } + private static NetworkTemplate createTemplate(int networkType, String subscriberId) { final NetworkTemplate template; switch (networkType) { diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 176a181965ed..a60e591dd0e6 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -163,7 +163,6 @@ public final class UsageStatsManager { /** * The app spent sufficient time in the old bucket without any substantial event so it reached * the timeout threshold to have its bucket lowered. - * * @hide */ public static final int REASON_MAIN_TIMEOUT = 0x0200; @@ -173,15 +172,25 @@ public final class UsageStatsManager { */ public static final int REASON_MAIN_USAGE = 0x0300; /** - * Forced by a core UID. + * Forced by the user/developer, either explicitly or implicitly through some action. If user + * action was not involved and this is purely due to the system, + * {@link #REASON_MAIN_FORCED_BY_SYSTEM} should be used instead. * @hide */ - public static final int REASON_MAIN_FORCED = 0x0400; + public static final int REASON_MAIN_FORCED_BY_USER = 0x0400; /** - * Set by a privileged system app. + * Set by a privileged system app. This may be overridden by + * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action. * @hide */ public static final int REASON_MAIN_PREDICTED = 0x0500; + /** + * Forced by the system, independent of user action. If user action is involved, + * {@link #REASON_MAIN_FORCED_BY_USER} should be used instead. When this is used, only + * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action can change the bucket. + * @hide + */ + public static final int REASON_MAIN_FORCED_BY_SYSTEM = 0x0600; /** @hide */ public static final int REASON_SUB_MASK = 0x00FF; @@ -1016,7 +1025,10 @@ public final class UsageStatsManager { case REASON_MAIN_DEFAULT: sb.append("d"); break; - case REASON_MAIN_FORCED: + case REASON_MAIN_FORCED_BY_SYSTEM: + sb.append("s"); + break; + case REASON_MAIN_FORCED_BY_USER: sb.append("f"); break; case REASON_MAIN_PREDICTED: diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java index e9b0be2c4cd6..a923be62fbce 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -16,8 +16,12 @@ package android.bluetooth; +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -36,6 +40,7 @@ import java.util.concurrent.Executor; */ public final class BluetoothHidDevice implements BluetoothProfile { private static final String TAG = BluetoothHidDevice.class.getSimpleName(); + private static final boolean DBG = false; /** * Intent used to broadcast the change in connection state of the Input Host profile. @@ -682,4 +687,62 @@ public final class BluetoothHidDevice implements BluetoothProfile { return result; } + + /** + * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} + * and disconnects Hid device if connectionPolicy is + * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}. + * + * <p> The device should already be paired. + * Connection policy can be one of: + * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, + * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, + * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy determines whether hid device should be connected or disconnected + * @return true if hid device is connected or disconnected, false otherwise + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + try { + final IBluetoothHidDevice service = getService(); + if (service != null && isEnabled() + && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + if (DBG) { + Log.d(TAG, msg); + } + } } diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 917e7fa04e4c..467470674286 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -17,7 +17,10 @@ package android.bluetooth; import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -35,21 +38,35 @@ import java.util.List; * * @hide */ +@SystemApi public final class BluetoothMap implements BluetoothProfile { private static final String TAG = "BluetoothMap"; private static final boolean DBG = true; private static final boolean VDBG = false; + /** @hide */ + @SuppressLint("ActionValue") + @SystemApi public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; - /** There was an error trying to obtain the state */ + /** + * There was an error trying to obtain the state + * + * @hide + */ public static final int STATE_ERROR = -1; + /** @hide */ public static final int RESULT_FAILURE = 0; + /** @hide */ public static final int RESULT_SUCCESS = 1; - /** Connection canceled before completion. */ + /** + * Connection canceled before completion. + * + * @hide + */ public static final int RESULT_CANCELED = 2; private BluetoothAdapter mAdapter; @@ -71,6 +88,7 @@ public final class BluetoothMap implements BluetoothProfile { mProfileConnector.connect(context, listener); } + @SuppressLint("GenericException") protected void finalize() throws Throwable { try { close(); @@ -84,6 +102,8 @@ public final class BluetoothMap implements BluetoothProfile { * Other public functions of BluetoothMap will return default error * results once close() has been called. Multiple invocations of close() * are ok. + * + * @hide */ public synchronized void close() { mProfileConnector.disconnect(); @@ -98,6 +118,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not * connected to the Map service. + * + * @hide */ public int getState() { if (VDBG) log("getState()"); @@ -120,6 +142,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @return The remote Bluetooth device, or null if not in connected or connecting state, or if * this proxy object is not connected to the Map service. + * + * @hide */ public BluetoothDevice getClient() { if (VDBG) log("getClient()"); @@ -141,6 +165,8 @@ public final class BluetoothMap implements BluetoothProfile { * Returns true if the specified Bluetooth device is connected. * Returns false if not connected, or if this proxy object is not * currently connected to the Map service. + * + * @hide */ public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); @@ -161,6 +187,8 @@ public final class BluetoothMap implements BluetoothProfile { /** * Initiate connection. Initiation of outgoing connections is not * supported for MAP server. + * + * @hide */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); @@ -172,6 +200,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @param device Remote Bluetooth Device * @return false on error, true otherwise + * + * @hide */ @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { @@ -196,6 +226,8 @@ public final class BluetoothMap implements BluetoothProfile { * devices. It tries to err on the side of false positives. * * @return True if this device might support Map. + * + * @hide */ public static boolean doesClassMatchSink(BluetoothClass btClass) { // TODO optimize the rule @@ -214,8 +246,11 @@ public final class BluetoothMap implements BluetoothProfile { * Get the list of connected devices. Currently at most one. * * @return list of connected devices + * + * @hide */ - public List<BluetoothDevice> getConnectedDevices() { + @SystemApi + public @NonNull List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); final IBluetoothMap service = getService(); if (service != null && isEnabled()) { @@ -234,6 +269,8 @@ public final class BluetoothMap implements BluetoothProfile { * Get the list of devices matching specified states. Currently at most one. * * @return list of matching devices + * + * @hide */ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); @@ -254,6 +291,8 @@ public final class BluetoothMap implements BluetoothProfile { * Get connection state of device * * @return device connection state + * + * @hide */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); @@ -301,7 +340,7 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -349,7 +388,7 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 42f27f2a9c98..024bb06098ab 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -16,9 +16,11 @@ package android.bluetooth; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; @@ -256,6 +258,41 @@ public final class BluetoothPan implements BluetoothProfile { } /** + * Set connection policy of the profile + * + * <p> The device should already be paired. + * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, + * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy is the connection policy to set to for this profile + * @return true if connectionPolicy is set, false on error + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + try { + final IBluetoothPan service = getService(); + if (service != null && isEnabled() + && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + /** * {@inheritDoc} */ @Override diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index 948885ed5010..e07ca521e77d 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -274,15 +274,15 @@ public class BluetoothPbap implements BluetoothProfile { } /** - * Pbap does not store connection policy, so this function only disconnects Pbap if - * connectionPolicy is CONNECTION_POLICY_FORBIDDEN. + * Pbap does not store connection policy, so this function only disconnects pbap if + * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}. * * <p> The device should already be paired. * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} * * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile + * @param connectionPolicy determines whether to disconnect the device * @return true if pbap is successfully disconnected, false otherwise * @hide */ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5cb2907dfba4..9ef95743b0c9 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3240,15 +3240,40 @@ public abstract class Context { } /** - * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle - * argument for use by system server and other multi-user aware code. - * @hide + * Binds to a service in the given {@code user} in the same manner as + * {@link #bindService(Intent, ServiceConnection, int)}. + * + * <p>If the given {@code user} is in the same profile group and the target package is the + * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is + * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS} + * for interacting with other users. + * + * @param service Identifies the service to connect to. The Intent must + * specify an explicit component name. + * @param conn Receives information as the service is started and stopped. + * This must be a valid ServiceConnection object; it must not be null. + * @param flags Operation options for the binding. May be 0, + * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND}, + * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT}, + * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}. + * {@link #BIND_IMPORTANT}, or + * {@link #BIND_ADJUST_WITH_ACTIVITY}. + * @return {@code true} if the system is in the process of bringing up a + * service that your client has permission to bind to; {@code false} + * if the system couldn't find the service. If this value is {@code true}, you + * should later call {@link #unbindService} to release the + * connection. + * + * @throws SecurityException if the client does not have the required permission to bind. */ - @SystemApi @SuppressWarnings("unused") - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) - public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn, - int flags, UserHandle user) { + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) + public boolean bindServiceAsUser( + @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags, + @NonNull UserHandle user) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -3928,10 +3953,12 @@ public abstract class Context { /** * Use with {@link android.os.ServiceManager.getService()} to retrieve a - * {@link NetworkStackClient} IBinder for communicating with the network stack + * {@link INetworkStackConnector} IBinder for communicating with the network stack * @hide * @see NetworkStackClient */ + @SystemApi + @TestApi public static final String NETWORK_STACK_SERVICE = "network_stack"; /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 3bb0f9222237..c8f587f71bca 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6440,19 +6440,22 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000; /** - * If set, when sending a broadcast <i>before boot has completed</i> only + * If set, when sending a broadcast <i>before the system has fully booted up + * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only * registered receivers will be called -- no BroadcastReceiver components * will be launched. Sticky intent state will be recorded properly even * if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY} * is specified in the broadcast intent, this flag is unnecessary. * - * <p>This flag is only for use by system sevices as a convenience to - * avoid having to implement a more complex mechanism around detection + * <p>This flag is only for use by system services (even services from mainline modules) as a + * convenience to avoid having to implement a more complex mechanism around detection * of boot completion. * + * <p>This is useful to system server mainline modules + * * @hide */ - @UnsupportedAppUsage + @SystemApi public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000; /** * Set when this broadcast is for a boot upgrade, a special mode that diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index abf32c5e0840..9d5751480a80 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -16,15 +16,19 @@ package android.content.pm; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import com.android.internal.R; import com.android.internal.util.UserIcons; @@ -226,6 +230,30 @@ public class CrossProfileApps { } } + /** + * Returns an {@link Intent} to open the settings page that allows the user to decide whether + * the calling app can interact across profiles. The current state is given by + * {@link #canInteractAcrossProfiles()}. + * + * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}. + * + * @return an {@link Intent} to open the settings page that allows the user to decide whether + * the app can interact across profiles + * + * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the + * calling UID. + */ + public @Nullable Intent createRequestInteractAcrossProfilesIntent() { + if (!canRequestInteractAcrossProfiles()) { + return null; + } + final Intent settingsIntent = new Intent(); + settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS); + final Uri packageUri = Uri.parse("package:" + mContext.getPackageName()); + settingsIntent.setData(packageUri); + return settingsIntent; + } + private void verifyCanAccessUser(UserHandle userHandle) { if (!getTargetUserProfiles().contains(userHandle)) { throw new SecurityException("Not allowed to access " + userHandle); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 32803ab8f859..87acbc146f49 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -7803,7 +7803,7 @@ public class PackageParser { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); - ai.resourceDirs = state.overlayPaths; + ai.resourceDirs = state.getAllOverlayPaths(); ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes; } diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index f0f6753fc93e..da7623a1426e 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -46,6 +46,8 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Map; import java.util.Objects; /** @@ -77,7 +79,9 @@ public class PackageUserState { public ArraySet<String> disabledComponents; public ArraySet<String> enabledComponents; - public String[] overlayPaths; + private String[] overlayPaths; + private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths + private String[] cachedOverlayPaths; @UnsupportedAppUsage public PackageUserState() { @@ -112,9 +116,33 @@ public class PackageUserState { enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents); overlayPaths = o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length); + if (o.sharedLibraryOverlayPaths != null) { + sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths); + } harmfulAppWarning = o.harmfulAppWarning; } + public String[] getOverlayPaths() { + return overlayPaths; + } + + public void setOverlayPaths(String[] paths) { + overlayPaths = paths; + cachedOverlayPaths = null; + } + + public Map<String, String[]> getSharedLibraryOverlayPaths() { + return sharedLibraryOverlayPaths; + } + + public void setSharedLibraryOverlayPaths(String library, String[] paths) { + if (sharedLibraryOverlayPaths == null) { + sharedLibraryOverlayPaths = new ArrayMap<>(); + } + sharedLibraryOverlayPaths.put(library, paths); + cachedOverlayPaths = null; + } + /** * Test if this package is installed. */ @@ -235,6 +263,38 @@ public class PackageUserState { return isComponentEnabled; } + public String[] getAllOverlayPaths() { + if (overlayPaths == null && sharedLibraryOverlayPaths == null) { + return null; + } + + if (cachedOverlayPaths != null) { + return cachedOverlayPaths; + } + + final LinkedHashSet<String> paths = new LinkedHashSet<>(); + if (overlayPaths != null) { + final int N = overlayPaths.length; + for (int i = 0; i < N; i++) { + paths.add(overlayPaths[i]); + } + } + + if (sharedLibraryOverlayPaths != null) { + for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) { + if (libOverlayPaths != null) { + final int N = libOverlayPaths.length; + for (int i = 0; i < N; i++) { + paths.add(libOverlayPaths[i]); + } + } + } + } + + cachedOverlayPaths = paths.toArray(new String[0]); + return cachedOverlayPaths; + } + @Override final public boolean equals(Object obj) { if (!(obj instanceof PackageUserState)) { diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java index f2cf9a484c6e..73a8d2a7dc0f 100644 --- a/core/java/android/content/pm/parsing/PackageInfoUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java @@ -41,9 +41,11 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation; import android.content.pm.parsing.ComponentParseUtils.ParsedPermission; import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup; +import android.util.ArraySet; import com.android.internal.util.ArrayUtils; +import java.util.LinkedHashSet; import java.util.Set; /** @hide */ @@ -545,7 +547,7 @@ public class PackageInfoUtils { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); - ai.resourceDirs = state.overlayPaths; + ai.resourceDirs = state.getAllOverlayPaths(); ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes; } diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING new file mode 100644 index 000000000000..daf9a1491cf9 --- /dev/null +++ b/core/java/android/content/res/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "FrameworksResourceLoaderTests" + } + ] +} diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java index 6378db0ebbbd..b273cd67479c 100644 --- a/core/java/android/content/rollback/PackageRollbackInfo.java +++ b/core/java/android/content/rollback/PackageRollbackInfo.java @@ -76,6 +76,11 @@ public final class PackageRollbackInfo implements Parcelable { */ private final boolean mIsApex; + /** + * Whether this instance represents the PackageRollbackInfo for an APK in APEX. + */ + private final boolean mIsApkInApex; + /* * The list of users for which snapshots have been saved. */ @@ -157,6 +162,10 @@ public final class PackageRollbackInfo implements Parcelable { public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() { return mRollbackDataPolicy; } + /** @hide */ + public boolean isApkInApex() { + return mIsApkInApex; + } /** @hide */ public IntArray getSnapshottedUsers() { @@ -190,17 +199,18 @@ public final class PackageRollbackInfo implements Parcelable { public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, - boolean isApex, @NonNull IntArray snapshottedUsers, + boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers, @NonNull SparseLongArray ceSnapshotInodes) { this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex, - snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE); + isApkInApex, snapshottedUsers, ceSnapshotInodes, + PackageManager.RollbackDataPolicy.RESTORE); } /** @hide */ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, - boolean isApex, @NonNull IntArray snapshottedUsers, + boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers, @NonNull SparseLongArray ceSnapshotInodes, @PackageManager.RollbackDataPolicy int rollbackDataPolicy) { this.mVersionRolledBackFrom = packageRolledBackFrom; @@ -209,6 +219,7 @@ public final class PackageRollbackInfo implements Parcelable { this.mPendingRestores = pendingRestores; this.mIsApex = isApex; this.mRollbackDataPolicy = rollbackDataPolicy; + this.mIsApkInApex = isApkInApex; this.mSnapshottedUsers = snapshottedUsers; this.mCeSnapshotInodes = ceSnapshotInodes; } @@ -217,6 +228,7 @@ public final class PackageRollbackInfo implements Parcelable { this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in); this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in); this.mIsApex = in.readBoolean(); + this.mIsApkInApex = in.readBoolean(); this.mPendingRestores = null; this.mPendingBackups = null; this.mSnapshottedUsers = null; @@ -234,6 +246,7 @@ public final class PackageRollbackInfo implements Parcelable { mVersionRolledBackFrom.writeToParcel(out, flags); mVersionRolledBackTo.writeToParcel(out, flags); out.writeBoolean(mIsApex); + out.writeBoolean(mIsApkInApex); } public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR = diff --git a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java index 07d2443ffacb..6ead64c02ef9 100644 --- a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java @@ -25,9 +25,12 @@ import java.util.List; * A constrained high speed capture session for a {@link CameraDevice}, used for capturing high * speed images from the {@link CameraDevice} for high speed video recording use case. * <p> - * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to - * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is - * active until a new session is created by the camera device, or the camera device is closed. + * A CameraConstrainedHighSpeedCaptureSession is created by providing a session configuration to + * {@link CameraDevice#createCaptureSession(SessionConfiguration)} with a type of + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED}. The + * CameraCaptureSession returned from {@link CameraCaptureSession.StateCallback} can then be cast to + * a CameraConstrainedHighSpeedCaptureSession. Once created, the session is active until a new + * session is created by the camera device, or the camera device is closed. * </p> * <p> * An active high speed capture session is a specialized capture session that is only targeted at @@ -37,8 +40,8 @@ import java.util.List; * accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can * only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See - * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the - * limitations. + * {@link CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} + * for more details of the limitations. * </p> * <p> * Creating a session is an expensive operation and can take several hundred milliseconds, since it @@ -50,13 +53,6 @@ import java.util.List; * completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and * the session will not become active. * </p> - * <!-- - * <p> - * Any capture requests (repeating or non-repeating) submitted before the session is ready will be - * queued up and will begin capture once the session becomes ready. In case the session cannot be - * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is - * called, all queued capture requests are discarded. </p> - * --> * <p> * If a new session is created by the camera device, then the previous session is closed, and its * associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index fb1ece29a369..cc066812ba1f 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -16,24 +16,22 @@ package android.hardware.camera2; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.TestApi; -import static android.hardware.camera2.ICameraDeviceUser.NORMAL_MODE; -import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE; import android.hardware.camera2.params.InputConfiguration; -import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Handler; import android.view.Surface; -import java.util.List; -import java.util.Set; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Set; /** * <p>The CameraDevice class is a representation of a single camera connected to an @@ -220,6 +218,224 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Create a new camera capture session by providing the target output set of Surfaces to the * camera device.</p> * + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, + * the callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CameraCaptureSession + * @see StreamConfigurationMap#getOutputFormats() + * @see StreamConfigurationMap#getOutputSizes(int) + * @see StreamConfigurationMap#getOutputSizes(Class) + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createCaptureSession(@NonNull List<Surface> outputs, + @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) + throws CameraAccessException; + + /** + * <p>Create a new camera capture session by providing the target output set of Surfaces and + * its corresponding surface configuration to the camera device.</p> + * + * @see #createCaptureSession + * @see OutputConfiguration + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createCaptureSessionByOutputConfigurations( + List<OutputConfiguration> outputConfigurations, + CameraCaptureSession.StateCallback callback, @Nullable Handler handler) + throws CameraAccessException; + /** + * Create a new reprocessable camera capture session by providing the desired reprocessing + * input Surface configuration and the target output set of Surfaces to the camera device. + * + * @param inputConfig The configuration for the input {@link Surface} + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the input configuration is null or not supported, the set + * of output Surfaces do not meet the requirements, the + * callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createCaptureSession + * @see CameraCaptureSession + * @see StreamConfigurationMap#getInputFormats + * @see StreamConfigurationMap#getInputSizes + * @see StreamConfigurationMap#getValidOutputFormatsForInput + * @see StreamConfigurationMap#getOutputSizes + * @see android.media.ImageWriter + * @see android.media.ImageReader + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig, + @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * Create a new reprocessable camera capture session by providing the desired reprocessing + * input configuration and output {@link OutputConfiguration} + * to the camera device. + * + * @see #createReprocessableCaptureSession + * @see OutputConfiguration + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createReprocessableCaptureSessionByConfigurations( + @NonNull InputConfiguration inputConfig, + @NonNull List<OutputConfiguration> outputs, + @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * <p>Create a new constrained high speed capture session.</p> + * + * @param outputs The new set of Surfaces that should be made available as + * targets for captured high speed image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, + * the callback is null, or the handler is null but the current + * thread has no looper, or the camera device doesn't support + * high speed video capability. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createCaptureSession + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE + * @see StreamConfigurationMap#getHighSpeedVideoSizes + * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO + * @see CameraCaptureSession#captureBurst + * @see CameraCaptureSession#setRepeatingBurst + * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs, + @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * Standard camera operation mode. + * + * @see #createCustomCaptureSession + * @hide + */ + @SystemApi + @TestApi + public static final int SESSION_OPERATION_MODE_NORMAL = + 0; // ICameraDeviceUser.NORMAL_MODE; + + /** + * Constrained high-speed operation mode. + * + * @see #createCustomCaptureSession + * @hide + */ + @SystemApi + @TestApi + public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = + 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE; + + /** + * First vendor-specific operating mode + * + * @see #createCustomCaptureSession + * @hide + */ + @SystemApi + @TestApi + public static final int SESSION_OPERATION_MODE_VENDOR_START = + 0x8000; // ICameraDeviceUser.VENDOR_MODE_START; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value = + {SESSION_OPERATION_MODE_NORMAL, + SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED, + SESSION_OPERATION_MODE_VENDOR_START}) + public @interface SessionOperatingMode {}; + + /** + * Create a new camera capture session with a custom operating mode. + * + * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session + * is desired, or {@code null} otherwise. + * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be + * made available as targets for captured image data. + * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom + * vendor value or one of the SESSION_OPERATION_MODE_* values. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the input configuration is null or not supported, the set + * of output Surfaces do not meet the requirements, the + * callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createCaptureSession + * @see #createReprocessableCaptureSession + * @see CameraCaptureSession + * @see OutputConfiguration + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + * @hide + */ + @SystemApi + @TestApi + @Deprecated + public abstract void createCustomCaptureSession( + InputConfiguration inputConfig, + @NonNull List<OutputConfiguration> outputs, + @SessionOperatingMode int operatingMode, + @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper + * object that aggregates all supported parameters.</p> * <p>The active capture session determines the set of potential output Surfaces for * the camera device for each capture request. A given request may use all * or only some of the outputs. Once the CameraCaptureSession is created, requests can be @@ -308,11 +524,15 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Configuring a session with an empty or null list will close the current session, if * any. This can be used to release the current session's target surfaces for another use.</p> * + * <h3>Regular capture</h3> + * * <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when * a single output stream is configured, a given camera device may not be able to support all * combination of sizes, formats, and targets when multiple outputs are configured at once. The * tables below list the maximum guaranteed resolutions for combinations of streams and targets, - * given the capabilities of the camera device.</p> + * given the capabilities of the camera device. These are valid for when the + * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration + * input configuration} is not set and therefore no reprocessing is active.</p> * * <p>If an application tries to create a session using a set of targets that exceed the limits * described in the below tables, one of three possibilities may occur. First, the session may @@ -488,56 +708,22 @@ public abstract class CameraDevice implements AutoCloseable { * (either width or height) might not be supported, and capture session creation will fail if it * is not.</p> * - * @param outputs The new set of Surfaces that should be made available as - * targets for captured image data. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, - * the callback is null, or the handler is null but the current - * thread has no looper. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see CameraCaptureSession - * @see StreamConfigurationMap#getOutputFormats() - * @see StreamConfigurationMap#getOutputSizes(int) - * @see StreamConfigurationMap#getOutputSizes(Class) - */ - public abstract void createCaptureSession(@NonNull List<Surface> outputs, - @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) - throws CameraAccessException; - - /** - * <p>Create a new camera capture session by providing the target output set of Surfaces and - * its corresponding surface configuration to the camera device.</p> - * - * @see #createCaptureSession - * @see OutputConfiguration - */ - public abstract void createCaptureSessionByOutputConfigurations( - List<OutputConfiguration> outputConfigurations, - CameraCaptureSession.StateCallback callback, @Nullable Handler handler) - throws CameraAccessException; - /** - * Create a new reprocessable camera capture session by providing the desired reprocessing - * input Surface configuration and the target output set of Surfaces to the camera device. + * <h3>Reprocessing</h3> * * <p>If a camera device supports YUV reprocessing * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or PRIVATE * reprocessing - * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), besides - * the capture session created via {@link #createCaptureSession createCaptureSession}, the + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), the * application can also create a reprocessable capture session to submit reprocess capture - * requests in addition to regular capture requests. A reprocess capture request takes the next - * available buffer from the session's input Surface, and sends it through the camera device's - * processing pipeline again, to produce buffers for the request's target output Surfaces. No - * new image data is captured for a reprocess request. However the input buffer provided by - * the application must be captured previously by the same camera device in the same session - * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output - * images).</p> + * requests in addition to regular capture requests, by setting an + * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration + * input configuration} for the session. A reprocess capture request takes the next available + * buffer from the + * session's input Surface, and sends it through the camera device's processing pipeline again, + * to produce buffers for the request's target output Surfaces. No new image data is captured + * for a reprocess request. However the input buffer provided by the application must be + * captured previously by the same camera device in the same session directly (e.g. for + * Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output images).</p> * * <p>The active reprocessable capture session determines an input {@link Surface} and the set * of potential output Surfaces for the camera devices for each capture request. The application @@ -570,10 +756,7 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Starting from API level 30, recreating a reprocessable capture session will flush all the * queued but not yet processed buffers from the input surface.</p> * - * <p>The guaranteed stream configurations listed in - * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for - * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition, - * the configurations in the tables below are also guaranteed for creating a reprocessable + * <p>The configurations in the tables below are guaranteed for creating a reprocessable * capture session if the camera device supports YUV reprocessing or PRIVATE reprocessing. * However, not all output targets used to create a reprocessable session may be used in a * {@link CaptureRequest} simultaneously. For devices that support only 1 output target in a @@ -602,7 +785,7 @@ public abstract class CameraDevice implements AutoCloseable { * <p>LIMITED-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL} * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices * support at least the following stream combinations for creating a reprocessable capture - * session in addition to those listed in {@link #createCaptureSession createCaptureSession} for + * session in addition to those listed earlier for regular captures for * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices: * * <table> @@ -671,74 +854,30 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * - * <p>Clients can access the above mandatory stream combination tables via - * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> - * - * @param inputConfig The configuration for the input {@link Surface} - * @param outputs The new set of Surfaces that should be made available as - * targets for captured image data. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the input configuration is null or not supported, the set - * of output Surfaces do not meet the requirements, the - * callback is null, or the handler is null but the current - * thread has no looper. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see #createCaptureSession - * @see CameraCaptureSession - * @see StreamConfigurationMap#getInputFormats - * @see StreamConfigurationMap#getInputSizes - * @see StreamConfigurationMap#getValidOutputFormatsForInput - * @see StreamConfigurationMap#getOutputSizes - * @see android.media.ImageWriter - * @see android.media.ImageReader - */ - public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig, - @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * Create a new reprocessable camera capture session by providing the desired reprocessing - * input configuration and output {@link OutputConfiguration} - * to the camera device. - * - * @see #createReprocessableCaptureSession - * @see OutputConfiguration + * <h3>Constrained high-speed recording</h3> * - */ - public abstract void createReprocessableCaptureSessionByConfigurations( - @NonNull InputConfiguration inputConfig, - @NonNull List<OutputConfiguration> outputs, - @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * <p>Create a new constrained high speed capture session.</p> - * - * <p>The application can use normal capture session (created via {@link #createCaptureSession}) + * <p>The application can use a + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_REGULAR + * normal capture session} * for high speed capture if the desired high speed FPS ranges are advertised by * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API * semantics associated with normal capture sessions applies.</p> * - * <p>The method creates a specialized capture session that is only targeted at high speed - * video recording (>=120fps) use case if the camera device supports high speed video - * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains - * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}). - * Therefore, it has special characteristics compared with a normal capture session:</p> + * <p>A + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED + * high-speed capture session} + * can be use for high speed video recording (>=120fps) when the camera device supports high + * speed video capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} + * contains {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}). + * A constrained high-speed capture session has special limitations compared with a normal + * capture session:</p> * * <ul> * - * <li>In addition to the output target Surface requirements specified by the - * {@link #createCaptureSession} method, an active high speed capture session will support up - * to 2 output Surfaces, though the application might choose to configure just one Surface - * (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by + * <li>In addition to the output target Surface requirements specified above for regular + * captures, a high speed capture session will only support up to 2 output Surfaces, though + * the application might choose to configure just one Surface (e.g., preview only). All + * Surfaces must be either video encoder surfaces (acquired by * {@link android.media.MediaRecorder#getSurface} or * {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via @@ -774,116 +913,6 @@ public abstract class CameraDevice implements AutoCloseable { * * </ul> * - * @param outputs The new set of Surfaces that should be made available as - * targets for captured high speed image data. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, - * the callback is null, or the handler is null but the current - * thread has no looper, or the camera device doesn't support - * high speed video capability. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see #createCaptureSession - * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE - * @see StreamConfigurationMap#getHighSpeedVideoSizes - * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor - * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO - * @see CameraCaptureSession#captureBurst - * @see CameraCaptureSession#setRepeatingBurst - * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList - */ - public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs, - @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * Standard camera operation mode. - * - * @see #createCustomCaptureSession - * @hide - */ - @SystemApi - @TestApi - public static final int SESSION_OPERATION_MODE_NORMAL = - 0; // ICameraDeviceUser.NORMAL_MODE; - - /** - * Constrained high-speed operation mode. - * - * @see #createCustomCaptureSession - * @hide - */ - @SystemApi - @TestApi - public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = - 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE; - - /** - * First vendor-specific operating mode - * - * @see #createCustomCaptureSession - * @hide - */ - @SystemApi - @TestApi - public static final int SESSION_OPERATION_MODE_VENDOR_START = - 0x8000; // ICameraDeviceUser.VENDOR_MODE_START; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value = - {SESSION_OPERATION_MODE_NORMAL, - SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED, - SESSION_OPERATION_MODE_VENDOR_START}) - public @interface SessionOperatingMode {}; - - /** - * Create a new camera capture session with a custom operating mode. - * - * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session - * is desired, or {@code null} otherwise. - * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be - * made available as targets for captured image data. - * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom - * vendor value or one of the SESSION_OPERATION_MODE_* values. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the input configuration is null or not supported, the set - * of output Surfaces do not meet the requirements, the - * callback is null, or the handler is null but the current - * thread has no looper. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see #createCaptureSession - * @see #createReprocessableCaptureSession - * @see CameraCaptureSession - * @see OutputConfiguration - * @hide - */ - @SystemApi - @TestApi - public abstract void createCustomCaptureSession( - InputConfiguration inputConfig, - @NonNull List<OutputConfiguration> outputs, - @SessionOperatingMode int operatingMode, - @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper - * object that aggregates all supported parameters.</p> * * @param config A session configuration (see {@link SessionConfiguration}). * @@ -997,7 +1026,7 @@ public abstract class CameraDevice implements AutoCloseable { * * @see CaptureRequest.Builder * @see TotalCaptureResult - * @see CameraDevice#createReprocessableCaptureSession + * @see CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration) * @see android.media.ImageWriter */ @NonNull @@ -1028,7 +1057,8 @@ public abstract class CameraDevice implements AutoCloseable { * <p>This method performs a runtime check of a given {@link SessionConfiguration}. The result * confirms whether or not the passed session configuration can be successfully used to * create a camera capture session using - * {@link CameraDevice#createCaptureSession(SessionConfiguration)}. + * {@link CameraDevice#createCaptureSession( + * android.hardware.camera2.params.SessionConfiguration)}. * </p> * * <p>The method can be called at any point before, during and after active capture session. diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 8e0a46d52dd6..ec13a36ca1d2 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1121,12 +1121,16 @@ public abstract class CameraMetadata<TKey> { // /** - * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, - * but can not be compared to timestamps from other subsystems - * (e.g. accelerometer, gyro etc.), or other instances of the same or different - * camera devices in the same system. Timestamps between streams and results for - * a single camera instance are comparable, and the timestamps for all buffers - * and the result metadata generated by a single capture are identical.</p> + * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, but can + * not be compared to timestamps from other subsystems (e.g. accelerometer, gyro etc.), + * or other instances of the same or different camera devices in the same system with + * accuracy. However, the timestamps are roughly in the same timebase as + * {@link android.os.SystemClock#uptimeMillis }. The accuracy is sufficient for tasks + * like A/V synchronization for video recording, at least, and the timestamps can be + * directly used together with timestamps from the audio subsystem for that task.</p> + * <p>Timestamps between streams and results for a single camera instance are comparable, + * and the timestamps for all buffers and the result metadata generated by a single + * capture are identical.</p> * * @see CaptureResult#SENSOR_TIMESTAMP * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE @@ -1137,6 +1141,14 @@ public abstract class CameraMetadata<TKey> { * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as * {@link android.os.SystemClock#elapsedRealtimeNanos }, * and they can be compared to other timestamps using that base.</p> + * <p>When buffers from a REALTIME device are passed directly to a video encoder from the + * camera, automatic compensation is done to account for differing timebases of the + * audio and camera subsystems. If the application is receiving buffers and then later + * sending them to a video encoder or other application where they are compared with + * audio subsystem timestamps or similar, this compensation is not present. In those + * cases, applications need to adjust the timestamps themselves. Since {@link android.os.SystemClock#elapsedRealtimeNanos } and {@link android.os.SystemClock#uptimeMillis } only diverge while the device is asleep, an + * offset between the two sources can be measured once per active session and applied + * to timestamps for sufficient accuracy for A/V sync.</p> * * @see CaptureResult#SENSOR_TIMESTAMP * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 555ff9aff184..47a897cb2c55 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -17,10 +17,11 @@ package android.hardware.camera2.params; +import static com.android.internal.util.Preconditions.*; + import android.annotation.CallbackExecutor; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.IntDef; +import android.annotation.NonNull; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; @@ -32,14 +33,12 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.ArrayList; import java.util.concurrent.Executor; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import static com.android.internal.util.Preconditions.*; /** * A helper class that aggregates all supported arguments for capture session initialization. @@ -61,6 +60,12 @@ public final class SessionConfiguration implements Parcelable { * A high speed session type that can only contain instances of {@link OutputConfiguration}. * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration} * are not supported. + * <p> + * When using this type, the CameraCaptureSession returned by + * {@link android.hardware.camera2.CameraCaptureSession.StateCallback} can be cast to a + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession} to access the extra + * methods for constrained high speed recording. + * </p> * * @see CameraDevice#createConstrainedHighSpeedCaptureSession */ diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 8231c58a105e..d43a619a7c1f 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -16,9 +16,11 @@ package android.hardware.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.ModelParams; import android.media.AudioFormat; import android.media.audio.common.AudioConfig; +import android.media.soundtrigger_middleware.AudioCapabilities; import android.media.soundtrigger_middleware.ConfidenceLevel; import android.media.soundtrigger_middleware.ModelParameterRange; import android.media.soundtrigger_middleware.Phrase; @@ -32,8 +34,6 @@ import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; -import android.annotation.Nullable; - import java.util.Arrays; import java.util.UUID; @@ -48,6 +48,7 @@ class ConversionUtil { properties.description, properties.uuid, properties.version, + properties.supportedModelArch, properties.maxSoundModels, properties.maxKeyPhrases, properties.maxUsers, @@ -56,7 +57,8 @@ class ConversionUtil { properties.maxBufferMs, properties.concurrentCapture, properties.powerConsumptionMw, - properties.triggerInEvent + properties.triggerInEvent, + aidl2apiAudioCapabilities(properties.audioCapabilities) ); } @@ -145,6 +147,7 @@ class ConversionUtil { apiConfig.keyphrases[i]); } aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length); + aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities); return aidlConfig; } @@ -326,4 +329,26 @@ class ConversionUtil { } return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive); } + + public static int aidl2apiAudioCapabilities(int aidlCapabilities) { + int result = 0; + if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) { + result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + } + if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) { + result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + } + return result; + } + + public static int api2aidlAudioCapabilities(int apiCapabilities) { + int result = 0; + if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) { + result |= AudioCapabilities.ECHO_CANCELLATION; + } + if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) { + result |= AudioCapabilities.NOISE_SUPPRESSION; + } + return result; + } } diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 55505ba2e278..a5e0f04eb034 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -24,6 +24,7 @@ import static android.system.OsConstants.EPIPE; import static java.util.Objects.requireNonNull; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -42,6 +43,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.UUID; @@ -83,6 +86,30 @@ public class SoundTrigger { * ****************************************************************************/ public static final class ModuleProperties implements Parcelable { + + /** + * Bit field values of AudioCapabilities supported by the implemented HAL + * driver. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { + CAPABILITY_ECHO_CANCELLATION, + CAPABILITY_NOISE_SUPPRESSION + }) + public @interface AudioCapabilities {} + + /** + * If set the underlying module supports AEC. + * Describes bit field {@link ModuleProperties#audioCapabilities} + */ + public static final int CAPABILITY_ECHO_CANCELLATION = 0x1; + /** + * If set, the underlying module supports noise suppression. + * Describes bit field {@link ModuleProperties#audioCapabilities} + */ + public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2; + /** Unique module ID provided by the native service */ public final int id; @@ -101,6 +128,14 @@ public class SoundTrigger { /** Voice detection engine version */ public final int version; + /** + * String naming the architecture used for running the supported models. + * (eg. a platform running models on a DSP could implement this string to convey the DSP + * architecture used) + */ + @NonNull + public final String supportedModelArch; + /** Maximum number of active sound models */ public final int maxSoundModels; @@ -129,16 +164,25 @@ public class SoundTrigger { * recognition callback event */ public final boolean returnsTriggerInEvent; + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + */ + @AudioCapabilities + public final int audioCapabilities; + ModuleProperties(int id, @NonNull String implementor, @NonNull String description, - @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases, - int maxUsers, int recognitionModes, boolean supportsCaptureTransition, - int maxBufferMs, boolean supportsConcurrentCapture, - int powerConsumptionMw, boolean returnsTriggerInEvent) { + @NonNull String uuid, int version, @NonNull String supportedModelArch, + int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes, + boolean supportsCaptureTransition, int maxBufferMs, + boolean supportsConcurrentCapture, int powerConsumptionMw, + boolean returnsTriggerInEvent, int audioCapabilities) { this.id = id; this.implementor = requireNonNull(implementor); this.description = requireNonNull(description); this.uuid = UUID.fromString(requireNonNull(uuid)); this.version = version; + this.supportedModelArch = requireNonNull(supportedModelArch); this.maxSoundModels = maxSoundModels; this.maxKeyphrases = maxKeyphrases; this.maxUsers = maxUsers; @@ -148,6 +192,7 @@ public class SoundTrigger { this.supportsConcurrentCapture = supportsConcurrentCapture; this.powerConsumptionMw = powerConsumptionMw; this.returnsTriggerInEvent = returnsTriggerInEvent; + this.audioCapabilities = audioCapabilities; } public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR @@ -167,6 +212,7 @@ public class SoundTrigger { String description = in.readString(); String uuid = in.readString(); int version = in.readInt(); + String supportedModelArch = in.readString(); int maxSoundModels = in.readInt(); int maxKeyphrases = in.readInt(); int maxUsers = in.readInt(); @@ -176,10 +222,11 @@ public class SoundTrigger { boolean supportsConcurrentCapture = in.readByte() == 1; int powerConsumptionMw = in.readInt(); boolean returnsTriggerInEvent = in.readByte() == 1; + int audioCapabilities = in.readInt(); return new ModuleProperties(id, implementor, description, uuid, version, - maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, + supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture, - powerConsumptionMw, returnsTriggerInEvent); + powerConsumptionMw, returnsTriggerInEvent, audioCapabilities); } @Override @@ -189,6 +236,7 @@ public class SoundTrigger { dest.writeString(description); dest.writeString(uuid.toString()); dest.writeInt(version); + dest.writeString(supportedModelArch); dest.writeInt(maxSoundModels); dest.writeInt(maxKeyphrases); dest.writeInt(maxUsers); @@ -198,6 +246,7 @@ public class SoundTrigger { dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0)); dest.writeInt(powerConsumptionMw); dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0)); + dest.writeInt(audioCapabilities); } @Override @@ -208,13 +257,15 @@ public class SoundTrigger { @Override public String toString() { return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description=" - + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels=" + + description + ", uuid=" + uuid + ", version=" + version + + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels=" + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers=" + maxUsers + ", recognitionModes=" + recognitionModes + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs=" + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture + ", powerConsumptionMw=" + powerConsumptionMw - + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]"; + + ", returnsTriggerInEvent=" + returnsTriggerInEvent + + ", audioCapabilities=" + audioCapabilities + "]"; } } @@ -252,16 +303,20 @@ public class SoundTrigger { @NonNull public final UUID vendorUuid; + /** vendor specific version number of the model */ + public final int version; + /** Opaque data. For use by vendor implementation and enrollment application */ @UnsupportedAppUsage @NonNull public final byte[] data; public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type, - @Nullable byte[] data) { + @Nullable byte[] data, int version) { this.uuid = requireNonNull(uuid); this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0); this.type = type; + this.version = version; this.data = data != null ? data : new byte[0]; } @@ -269,6 +324,7 @@ public class SoundTrigger { public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + version; result = prime * result + Arrays.hashCode(data); result = prime * result + type; result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); @@ -299,6 +355,8 @@ public class SoundTrigger { return false; if (!Arrays.equals(data, other.data)) return false; + if (version != other.version) + return false; return true; } } @@ -448,14 +506,19 @@ public class SoundTrigger { @NonNull public final Keyphrase[] keyphrases; // keyword phrases in model - @UnsupportedAppUsage public KeyphraseSoundModel( @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, - @Nullable Keyphrase[] keyphrases) { - super(uuid, vendorUuid, TYPE_KEYPHRASE, data); + @Nullable Keyphrase[] keyphrases, int version) { + super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version); this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0]; } + @UnsupportedAppUsage + public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) { + this(uuid, vendorUuid, data, keyphrases, -1); + } + public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR = new Parcelable.Creator<KeyphraseSoundModel>() { public KeyphraseSoundModel createFromParcel(Parcel in) { @@ -474,9 +537,10 @@ public class SoundTrigger { if (length >= 0) { vendorUuid = UUID.fromString(in.readString()); } + int version = in.readInt(); byte[] data = in.readBlob(); Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR); - return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases); + return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version); } @Override @@ -495,13 +559,16 @@ public class SoundTrigger { } dest.writeBlob(data); dest.writeTypedArray(keyphrases, flags); + dest.writeInt(version); } @Override public String toString() { return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases) + ", uuid=" + uuid + ", vendorUuid=" + vendorUuid - + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]"; + + ", type=" + type + + ", data=" + (data == null ? 0 : data.length) + + ", version=" + version + "]"; } @Override @@ -547,10 +614,15 @@ public class SoundTrigger { } }; + public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, int version) { + super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version); + } + @UnsupportedAppUsage public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data) { - super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data); + this(uuid, vendorUuid, data, -1); } @Override @@ -566,7 +638,8 @@ public class SoundTrigger { vendorUuid = UUID.fromString(in.readString()); } byte[] data = in.readBlob(); - return new GenericSoundModel(uuid, vendorUuid, data); + int version = in.readInt(); + return new GenericSoundModel(uuid, vendorUuid, data, version); } @Override @@ -579,28 +652,31 @@ public class SoundTrigger { dest.writeString(vendorUuid.toString()); } dest.writeBlob(data); + dest.writeInt(version); } @Override public String toString() { return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid - + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]"; + + ", type=" + type + + ", data=" + (data == null ? 0 : data.length) + + ", version=" + version + "]"; } } - /***************************************************************************** + /** * A ModelParamRange is a representation of supported parameter range for a * given loaded model. - ****************************************************************************/ + */ public static final class ModelParamRange implements Parcelable { /** - * start of supported range inclusive + * The inclusive start of supported range. */ public final int start; /** - * end of supported range inclusive + * The inclusive end of supported range. */ public final int end; @@ -609,31 +685,65 @@ public class SoundTrigger { this.end = end; } + /** @hide */ private ModelParamRange(@NonNull Parcel in) { this.start = in.readInt(); this.end = in.readInt(); } @NonNull - public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() { - @Override - @NonNull - public ModelParamRange createFromParcel(@NonNull Parcel in) { - return new ModelParamRange(in); - } - - @Override - @NonNull - public ModelParamRange[] newArray(int size) { - return new ModelParamRange[size]; - } - }; + public static final Creator<ModelParamRange> CREATOR = + new Creator<ModelParamRange>() { + @Override + @NonNull + public ModelParamRange createFromParcel(@NonNull Parcel in) { + return new ModelParamRange(in); + } + + @Override + @NonNull + public ModelParamRange[] newArray(int size) { + return new ModelParamRange[size]; + } + }; + /** @hide */ @Override public int describeContents() { return 0; } + /** @hide */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (start); + result = prime * result + (end); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ModelParamRange other = (ModelParamRange) obj; + if (start != other.start) { + return false; + } + if (end != other.end) { + return false; + } + return true; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(start); @@ -1002,13 +1112,27 @@ public class SoundTrigger { @NonNull public final byte[] data; - @UnsupportedAppUsage + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + */ + @ModuleProperties.AudioCapabilities + public final int audioCapabilities; + public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, - @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { + @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, + int audioCapabilities) { this.captureRequested = captureRequested; this.allowMultipleTriggers = allowMultipleTriggers; this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; this.data = data != null ? data : new byte[0]; + this.audioCapabilities = audioCapabilities; + } + + @UnsupportedAppUsage + public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, + @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { + this(captureRequested, allowMultipleTriggers, keyphrases, data, 0); } public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR @@ -1028,7 +1152,9 @@ public class SoundTrigger { KeyphraseRecognitionExtra[] keyphrases = in.createTypedArray(KeyphraseRecognitionExtra.CREATOR); byte[] data = in.readBlob(); - return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data); + int audioCapabilities = in.readInt(); + return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data, + audioCapabilities); } @Override @@ -1037,6 +1163,7 @@ public class SoundTrigger { dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); dest.writeTypedArray(keyphrases, flags); dest.writeBlob(data); + dest.writeInt(audioCapabilities); } @Override @@ -1048,7 +1175,8 @@ public class SoundTrigger { public String toString() { return "RecognitionConfig [captureRequested=" + captureRequested + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases=" - + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]"; + + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]"; } } diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java index a66fcae7d4a2..fb35b4bde303 100644 --- a/core/java/android/net/CaptivePortal.java +++ b/core/java/android/net/CaptivePortal.java @@ -60,6 +60,18 @@ public class CaptivePortal implements Parcelable { @SystemApi @TestApi public static final int APP_RETURN_WANTED_AS_IS = 2; + /** Event offset of request codes from captive portal application. */ + private static final int APP_REQUEST_BASE = 100; + /** + * Request code from the captive portal application, indicating that the network condition may + * have changed and the network should be re-validated. + * @see ICaptivePortal#appRequest(int) + * @see android.net.INetworkMonitor#forceReevaluation(int) + * @hide + */ + @SystemApi + @TestApi + public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0; private final IBinder mBinder; @@ -136,6 +148,19 @@ public class CaptivePortal implements Parcelable { } /** + * Request that the system reevaluates the captive portal status. + * @hide + */ + @SystemApi + @TestApi + public void reevaluateNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED); + } catch (RemoteException e) { + } + } + + /** * Log a captive portal login event. * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. * @param packageName captive portal application package name. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 03d4200bd90f..c523f6514c54 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -517,7 +517,7 @@ public class ConnectivityManager { * The absence of a connection type. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + @SystemApi public static final int TYPE_NONE = -1; /** @@ -3121,8 +3121,6 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull NetworkProvider provider) { if (provider.getProviderId() != NetworkProvider.ID_NONE) { - // TODO: Provide a better method for checking this by moving NetworkFactory.SerialNumber - // out of NetworkFactory. throw new IllegalStateException("NetworkProviders can only be registered once"); } @@ -3171,26 +3169,24 @@ public class ConnectivityManager { /** * @hide * Register a NetworkAgent with ConnectivityService. - * @return NetID corresponding to NetworkAgent. + * @return Network corresponding to NetworkAgent. */ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkMisc misc) { - return registerNetworkAgent(messenger, ni, lp, nc, score, misc, - NetworkFactory.SerialNumber.NONE); + public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, + NetworkCapabilities nc, int score, NetworkAgentConfig config) { + return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE); } /** * @hide * Register a NetworkAgent with ConnectivityService. - * @return NetID corresponding to NetworkAgent. + * @return Network corresponding to NetworkAgent. */ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) { + public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, + NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { try { - return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc, - factorySerialNumber); + return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl index 707b4f699873..fe21905c7002 100644 --- a/core/java/android/net/ICaptivePortal.aidl +++ b/core/java/android/net/ICaptivePortal.aidl @@ -21,6 +21,7 @@ package android.net; * @hide */ oneway interface ICaptivePortal { + void appRequest(int request); void appResponse(int response); void logEvent(int eventId, String packageName); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index e6a0379ff629..186196bd31c7 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -20,9 +20,9 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.net.NetworkMisc; import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkState; @@ -152,8 +152,9 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); - int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, - in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber); + Network registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, + in NetworkCapabilities nc, int score, in NetworkAgentConfig config, + in int factorySerialNumber); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, in Messenger messenger, int timeoutSec, in IBinder binder, int legacy); diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 9994f9f82b07..5fa515a14343 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -23,6 +23,8 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.netstats.provider.INetworkStatsProvider; +import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.IBinder; import android.os.Messenger; import com.android.internal.net.VpnInfo; @@ -89,4 +91,7 @@ interface INetworkStatsService { /** Get the total network stats information since boot */ long getTotalStats(int type); + /** Registers a network stats provider */ + INetworkStatsProviderCallback registerNetworkStatsProvider(String tag, + in INetworkStatsProvider provider); } diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java index 3fb52f12a88d..bd39c13ba092 100644 --- a/core/java/android/net/NattKeepalivePacketData.java +++ b/core/java/android/net/NattKeepalivePacketData.java @@ -16,9 +16,11 @@ package android.net; -import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; -import static android.net.SocketKeepalive.ERROR_INVALID_PORT; +import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS; +import static android.net.InvalidPacketException.ERROR_INVALID_PORT; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.net.util.IpUtils; import android.os.Parcel; import android.os.Parcelable; @@ -30,20 +32,22 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; /** @hide */ +@SystemApi public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable { private static final int IPV4_HEADER_LENGTH = 20; private static final int UDP_HEADER_LENGTH = 8; // This should only be constructed via static factory methods, such as // nattKeepalivePacket - private NattKeepalivePacketData(InetAddress srcAddress, int srcPort, - InetAddress dstAddress, int dstPort, byte[] data) throws + public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort, + @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws InvalidPacketException { super(srcAddress, srcPort, dstAddress, dstPort, data); } /** * Factory method to create Nat-T keepalive packet structure. + * @hide */ public static NattKeepalivePacketData nattKeepalivePacket( InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) @@ -87,7 +91,7 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement } /** Write to parcel */ - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeString(srcAddress.getHostAddress()); out.writeString(dstAddress.getHostAddress()); out.writeInt(srcPort); @@ -95,7 +99,7 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement } /** Parcelable Creator */ - public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR = + public static final @NonNull Parcelable.Creator<NattKeepalivePacketData> CREATOR = new Parcelable.Creator<NattKeepalivePacketData>() { public NattKeepalivePacketData createFromParcel(Parcel in) { final InetAddress srcAddress = diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 60bd5730f8cf..d28620433c21 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -43,11 +43,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * * @hide */ -public abstract class NetworkAgent extends Handler { - // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown - // an exception. - public final int netId; +public abstract class NetworkAgent { + // Guaranteed to be non-null, otherwise registerNetworkAgent() would have thrown + // an exception. Be careful in tests when mocking though. + @NonNull + public final Network network; + private final Handler mHandler; private volatile AsyncChannel mAsyncChannel; private final String LOG_TAG; private static final boolean DBG = true; @@ -58,7 +60,7 @@ public abstract class NetworkAgent extends Handler { private static final long BW_REFRESH_MIN_WIN_MS = 500; private boolean mPollLceScheduled = false; private AtomicBoolean mPollLcePending = new AtomicBoolean(false); - public final int mFactorySerialNumber; + public final int mProviderId; private static final int BASE = Protocol.BASE_NETWORK_AGENT; @@ -219,25 +221,25 @@ public abstract class NetworkAgent extends Handler { // the entire tree. public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { - this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE); + this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE); } public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, - NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) { - this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE); + NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) { + this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE); } public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, - NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) { - this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber); + NetworkCapabilities nc, LinkProperties lp, int score, int providerId) { + this(looper, context, logTag, ni, nc, lp, score, null, providerId); } public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, - NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc, - int factorySerialNumber) { - super(looper); + NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config, + int providerId) { + mHandler = new NetworkAgentHandler(looper); LOG_TAG = logTag; mContext = context; - mFactorySerialNumber = factorySerialNumber; + mProviderId = providerId; if (ni == null || nc == null || lp == null) { throw new IllegalArgumentException(); } @@ -245,117 +247,124 @@ public abstract class NetworkAgent extends Handler { if (VDBG) log("Registering NetworkAgent"); ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); - netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), - new LinkProperties(lp), new NetworkCapabilities(nc), score, misc, - factorySerialNumber); + network = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni), + new LinkProperties(lp), new NetworkCapabilities(nc), score, config, + providerId); } - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - if (mAsyncChannel != null) { - log("Received new connection while already connected!"); - } else { - if (VDBG) log("NetworkAgent fully connected"); - AsyncChannel ac = new AsyncChannel(); - ac.connected(null, this, msg.replyTo); - ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); - synchronized (mPreConnectedQueue) { - mAsyncChannel = ac; - for (Message m : mPreConnectedQueue) { - ac.sendMessage(m); + private class NetworkAgentHandler extends Handler { + NetworkAgentHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { + if (mAsyncChannel != null) { + log("Received new connection while already connected!"); + } else { + if (VDBG) log("NetworkAgent fully connected"); + AsyncChannel ac = new AsyncChannel(); + ac.connected(null, this, msg.replyTo); + ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, + AsyncChannel.STATUS_SUCCESSFUL); + synchronized (mPreConnectedQueue) { + mAsyncChannel = ac; + for (Message m : mPreConnectedQueue) { + ac.sendMessage(m); + } + mPreConnectedQueue.clear(); } - mPreConnectedQueue.clear(); } + break; } - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - if (VDBG) log("CMD_CHANNEL_DISCONNECT"); - if (mAsyncChannel != null) mAsyncChannel.disconnect(); - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { - if (DBG) log("NetworkAgent channel lost"); - // let the client know CS is done with us. - unwanted(); - synchronized (mPreConnectedQueue) { - mAsyncChannel = null; + case AsyncChannel.CMD_CHANNEL_DISCONNECT: { + if (VDBG) log("CMD_CHANNEL_DISCONNECT"); + if (mAsyncChannel != null) mAsyncChannel.disconnect(); + break; } - break; - } - case CMD_SUSPECT_BAD: { - log("Unhandled Message " + msg); - break; - } - case CMD_REQUEST_BANDWIDTH_UPDATE: { - long currentTimeMs = System.currentTimeMillis(); - if (VDBG) { - log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { + if (DBG) log("NetworkAgent channel lost"); + // let the client know CS is done with us. + unwanted(); + synchronized (mPreConnectedQueue) { + mAsyncChannel = null; + } + break; } - if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { - mPollLceScheduled = false; - if (mPollLcePending.getAndSet(true) == false) { - pollLceData(); + case CMD_SUSPECT_BAD: { + log("Unhandled Message " + msg); + break; + } + case CMD_REQUEST_BANDWIDTH_UPDATE: { + long currentTimeMs = System.currentTimeMillis(); + if (VDBG) { + log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); } - } else { - // deliver the request at a later time rather than discard it completely. - if (!mPollLceScheduled) { - long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS - - currentTimeMs + 1; - mPollLceScheduled = sendEmptyMessageDelayed( - CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); + if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { + mPollLceScheduled = false; + if (!mPollLcePending.getAndSet(true)) { + pollLceData(); + } + } else { + // deliver the request at a later time rather than discard it completely. + if (!mPollLceScheduled) { + long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS + - currentTimeMs + 1; + mPollLceScheduled = sendEmptyMessageDelayed( + CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); + } } + break; } - break; - } - case CMD_REPORT_NETWORK_STATUS: { - String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY); - if (VDBG) { - log("CMD_REPORT_NETWORK_STATUS(" + - (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl); + case CMD_REPORT_NETWORK_STATUS: { + String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY); + if (VDBG) { + log("CMD_REPORT_NETWORK_STATUS(" + + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + + redirectUrl); + } + networkStatus(msg.arg1, redirectUrl); + break; + } + case CMD_SAVE_ACCEPT_UNVALIDATED: { + saveAcceptUnvalidated(msg.arg1 != 0); + break; + } + case CMD_START_SOCKET_KEEPALIVE: { + startSocketKeepalive(msg); + break; + } + case CMD_STOP_SOCKET_KEEPALIVE: { + stopSocketKeepalive(msg); + break; } - networkStatus(msg.arg1, redirectUrl); - break; - } - case CMD_SAVE_ACCEPT_UNVALIDATED: { - saveAcceptUnvalidated(msg.arg1 != 0); - break; - } - case CMD_START_SOCKET_KEEPALIVE: { - startSocketKeepalive(msg); - break; - } - case CMD_STOP_SOCKET_KEEPALIVE: { - stopSocketKeepalive(msg); - break; - } - case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { - ArrayList<Integer> thresholds = - ((Bundle) msg.obj).getIntegerArrayList("thresholds"); - // TODO: Change signal strength thresholds API to use an ArrayList<Integer> - // rather than convert to int[]. - int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0]; - for (int i = 0; i < intThresholds.length; i++) { - intThresholds[i] = thresholds.get(i); + case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { + ArrayList<Integer> thresholds = + ((Bundle) msg.obj).getIntegerArrayList("thresholds"); + // TODO: Change signal strength thresholds API to use an ArrayList<Integer> + // rather than convert to int[]. + int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0]; + for (int i = 0; i < intThresholds.length; i++) { + intThresholds[i] = thresholds.get(i); + } + setSignalStrengthThresholds(intThresholds); + break; + } + case CMD_PREVENT_AUTOMATIC_RECONNECT: { + preventAutomaticReconnect(); + break; + } + case CMD_ADD_KEEPALIVE_PACKET_FILTER: { + addKeepalivePacketFilter(msg); + break; + } + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { + removeKeepalivePacketFilter(msg); + break; } - setSignalStrengthThresholds(intThresholds); - break; - } - case CMD_PREVENT_AUTOMATIC_RECONNECT: { - preventAutomaticReconnect(); - break; - } - case CMD_ADD_KEEPALIVE_PACKET_FILTER: { - addKeepalivePacketFilter(msg); - break; - } - case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { - removeKeepalivePacketFilter(msg); - break; } } } diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/NetworkAgentConfig.aidl index c65583fb530a..cb70bdd31260 100644 --- a/core/java/android/net/NetworkMisc.aidl +++ b/core/java/android/net/NetworkAgentConfig.aidl @@ -16,4 +16,4 @@ package android.net; -parcelable NetworkMisc; +parcelable NetworkAgentConfig; diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java new file mode 100644 index 000000000000..abc6b67efb11 --- /dev/null +++ b/core/java/android/net/NetworkAgentConfig.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * Allows a network transport to provide the system with policy and configuration information about + * a particular network when registering a {@link NetworkAgent}. This information cannot change once + * the agent is registered. + * + * @hide + */ +@SystemApi +public final class NetworkAgentConfig implements Parcelable { + + /** + * If the {@link Network} is a VPN, whether apps are allowed to bypass the + * VPN. This is set by a {@link VpnService} and used by + * {@link ConnectivityManager} when creating a VPN. + * + * @hide + */ + public boolean allowBypass; + + /** + * Set if the network was manually/explicitly connected to by the user either from settings + * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi + * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to + * connect to a particular access point is also explicit, though this may change in the future + * as we want apps to use the multinetwork apis. + * + * @hide + */ + public boolean explicitlySelected; + + /** + * Set if the user desires to use this network even if it is unvalidated. This field has meaning + * only if {@link explicitlySelected} is true. If it is, this field must also be set to the + * appropriate value based on previous user choice. + * + * @hide + */ + public boolean acceptUnvalidated; + + /** + * Whether the user explicitly set that this network should be validated even if presence of + * only partial internet connectivity. + * + * @hide + */ + public boolean acceptPartialConnectivity; + + /** + * Set to avoid surfacing the "Sign in to network" notification. + * if carrier receivers/apps are registered to handle the carrier-specific provisioning + * procedure, a carrier specific provisioning notification will be placed. + * only one notification should be displayed. This field is set based on + * which notification should be used for provisioning. + * + * @hide + */ + public boolean provisioningNotificationDisabled; + + /** + * + * @return whether the sign in to network notification is enabled by this configuration. + */ + public boolean isProvisioningNotificationEnabled() { + return !provisioningNotificationDisabled; + } + + /** + * For mobile networks, this is the subscriber ID (such as IMSI). + * + * @hide + */ + public String subscriberId; + + /** + * @return the subscriber ID, or null if none. + */ + @Nullable + public String getSubscriberId() { + return subscriberId; + } + + /** + * Set to skip 464xlat. This means the device will treat the network as IPv6-only and + * will not attempt to detect a NAT64 via RFC 7050 DNS lookups. + * + * @hide + */ + public boolean skip464xlat; + + /** + * @return whether NAT64 prefix detection is enabled. + */ + public boolean isNat64DetectionEnabled() { + return !skip464xlat; + } + + /** + * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. + * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. + * + * @hide + */ + public boolean hasShownBroken; + + /** @hide */ + public NetworkAgentConfig() { + } + + /** @hide */ + public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) { + if (nac != null) { + allowBypass = nac.allowBypass; + explicitlySelected = nac.explicitlySelected; + acceptUnvalidated = nac.acceptUnvalidated; + subscriberId = nac.subscriberId; + provisioningNotificationDisabled = nac.provisioningNotificationDisabled; + skip464xlat = nac.skip464xlat; + } + } + + /** + * Builder class to facilitate constructing {@link NetworkAgentConfig} objects. + */ + public static class Builder { + private final NetworkAgentConfig mConfig = new NetworkAgentConfig(); + + /** + * Sets the subscriber ID for this network. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setSubscriberId(@Nullable String subscriberId) { + mConfig.subscriberId = subscriberId; + return this; + } + + /** + * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power + * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder disableNat64Detection() { + mConfig.skip464xlat = true; + return this; + } + + /** + * Disables the "Sign in to network" notification. Used if the network transport will + * perform its own carrier-specific provisioning procedure. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder disableProvisioningNotification() { + mConfig.provisioningNotificationDisabled = true; + return this; + } + + /** + * Returns the constructed {@link NetworkAgentConfig} object. + */ + @NonNull + public NetworkAgentConfig build() { + return mConfig; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(allowBypass ? 1 : 0); + out.writeInt(explicitlySelected ? 1 : 0); + out.writeInt(acceptUnvalidated ? 1 : 0); + out.writeString(subscriberId); + out.writeInt(provisioningNotificationDisabled ? 1 : 0); + out.writeInt(skip464xlat ? 1 : 0); + } + + public static final @NonNull Creator<NetworkAgentConfig> CREATOR = + new Creator<NetworkAgentConfig>() { + @Override + public NetworkAgentConfig createFromParcel(Parcel in) { + NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); + networkAgentConfig.allowBypass = in.readInt() != 0; + networkAgentConfig.explicitlySelected = in.readInt() != 0; + networkAgentConfig.acceptUnvalidated = in.readInt() != 0; + networkAgentConfig.subscriberId = in.readString(); + networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0; + networkAgentConfig.skip464xlat = in.readInt() != 0; + return networkAgentConfig; + } + + @Override + public NetworkAgentConfig[] newArray(int size) { + return new NetworkAgentConfig[size]; + } + }; +} diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java index 42ab9256a6ab..824ddb8dd260 100644 --- a/core/java/android/net/NetworkFactory.java +++ b/core/java/android/net/NetworkFactory.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; @@ -27,7 +28,6 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Protocol; @@ -52,7 +52,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @hide **/ public class NetworkFactory extends Handler { - /** @hide */ + /* TODO: delete when all callers have migrated to NetworkProvider IDs. */ public static class SerialNumber { // Guard used by no network factory. public static final int NONE = -1; @@ -91,8 +91,8 @@ public class NetworkFactory extends Handler { * with the same NetworkRequest but an updated score. * Also, network conditions may change for this bearer * allowing for a better score in the future. - * msg.arg2 = the serial number of the factory currently responsible for the - * NetworkAgent handling this request, or SerialNumber.NONE if none. + * msg.arg2 = the ID of the NetworkProvider currently responsible for the + * NetworkAgent handling this request, or NetworkProvider.ID_NONE if none. */ public static final int CMD_REQUEST_NETWORK = BASE; @@ -124,7 +124,6 @@ public class NetworkFactory extends Handler { private final Context mContext; private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>(); - private AsyncChannel mAsyncChannel; private final String LOG_TAG; private final SparseArray<NetworkRequestInfo> mNetworkRequests = @@ -135,7 +134,8 @@ public class NetworkFactory extends Handler { private int mRefCount = 0; private Messenger mMessenger = null; - private int mSerialNumber; + private NetworkProvider mProvider = null; + private int mProviderId; @UnsupportedAppUsage public NetworkFactory(Looper looper, Context context, String logTag, @@ -147,55 +147,43 @@ public class NetworkFactory extends Handler { } public void register() { - if (DBG) log("Registering NetworkFactory"); - if (mMessenger == null) { - mMessenger = new Messenger(this); - mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, - LOG_TAG); + if (mProvider != null) { + Log.e(LOG_TAG, "Ignoring attempt to register already-registered NetworkFactory"); + return; } + if (DBG) log("Registering NetworkFactory"); + + mProvider = new NetworkProvider(mContext, NetworkFactory.this.getLooper(), LOG_TAG) { + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, + int servingProviderId) { + handleAddRequest((NetworkRequest) request, score, servingProviderId); + } + + @Override + public void onRequestWithdrawn(@NonNull NetworkRequest request) { + handleRemoveRequest(request); + } + }; + + mMessenger = new Messenger(this); + mProviderId = ConnectivityManager.from(mContext).registerNetworkProvider(mProvider); } public void unregister() { - if (DBG) log("Unregistering NetworkFactory"); - if (mMessenger != null) { - ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger); - mMessenger = null; + if (mProvider == null) { + Log.e(LOG_TAG, "Ignoring attempt to unregister unregistered NetworkFactory"); + return; } + if (DBG) log("Unregistering NetworkFactory"); + + ConnectivityManager.from(mContext).unregisterNetworkProvider(mProvider); + mProvider = null; } @Override public void handleMessage(Message msg) { switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - if (mAsyncChannel != null) { - log("Received new connection while already connected!"); - break; - } - if (VDBG) log("NetworkFactory fully connected"); - AsyncChannel ac = new AsyncChannel(); - ac.connected(null, this, msg.replyTo); - ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); - mAsyncChannel = ac; - for (Message m : mPreConnectedQueue) { - ac.sendMessage(m); - } - mPreConnectedQueue.clear(); - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - if (VDBG) log("CMD_CHANNEL_DISCONNECT"); - if (mAsyncChannel != null) { - mAsyncChannel.disconnect(); - mAsyncChannel = null; - } - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { - if (DBG) log("NetworkFactory channel lost"); - mAsyncChannel = null; - break; - } case CMD_REQUEST_NETWORK: { handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2); break; @@ -219,13 +207,13 @@ public class NetworkFactory extends Handler { public final NetworkRequest request; public int score; public boolean requested; // do we have a request outstanding, limited by score - public int factorySerialNumber; + public int providerId; - NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) { + NetworkRequestInfo(NetworkRequest request, int score, int providerId) { this.request = request; this.score = score; this.requested = false; - this.factorySerialNumber = factorySerialNumber; + this.providerId = providerId; } @Override @@ -240,8 +228,6 @@ public class NetworkFactory extends Handler { * * @param request the request to handle. * @param score the score of the NetworkAgent currently satisfying this request. - * @param servingFactorySerialNumber the serial number of the NetworkFactory that - * created the NetworkAgent currently satisfying this request. */ // TODO : remove this method. It is a stopgap measure to help sheperding a number // of dependent changes that would conflict throughout the automerger graph. Having this @@ -249,7 +235,7 @@ public class NetworkFactory extends Handler { // the entire tree. @VisibleForTesting protected void handleAddRequest(NetworkRequest request, int score) { - handleAddRequest(request, score, SerialNumber.NONE); + handleAddRequest(request, score, NetworkProvider.ID_NONE); } /** @@ -258,27 +244,26 @@ public class NetworkFactory extends Handler { * * @param request the request to handle. * @param score the score of the NetworkAgent currently satisfying this request. - * @param servingFactorySerialNumber the serial number of the NetworkFactory that - * created the NetworkAgent currently satisfying this request. + * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent + * currently satisfying this request. */ @VisibleForTesting - protected void handleAddRequest(NetworkRequest request, int score, - int servingFactorySerialNumber) { + protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) { NetworkRequestInfo n = mNetworkRequests.get(request.requestId); if (n == null) { if (DBG) { log("got request " + request + " with score " + score - + " and serial " + servingFactorySerialNumber); + + " and providerId " + servingProviderId); } - n = new NetworkRequestInfo(request, score, servingFactorySerialNumber); + n = new NetworkRequestInfo(request, score, servingProviderId); mNetworkRequests.put(n.request.requestId, n); } else { if (VDBG) { log("new score " + score + " for exisiting request " + request - + " with serial " + servingFactorySerialNumber); + + " and providerId " + servingProviderId); } n.score = score; - n.factorySerialNumber = servingFactorySerialNumber; + n.providerId = servingProviderId; } if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); @@ -333,8 +318,8 @@ public class NetworkFactory extends Handler { log(" n.requests = " + n.requested); log(" n.score = " + n.score); log(" mScore = " + mScore); - log(" n.factorySerialNumber = " + n.factorySerialNumber); - log(" mSerialNumber = " + mSerialNumber); + log(" n.providerId = " + n.providerId); + log(" mProviderId = " + mProviderId); } if (shouldNeedNetworkFor(n)) { if (VDBG) log(" needNetworkFor"); @@ -355,7 +340,7 @@ public class NetworkFactory extends Handler { // If the score of this request is higher or equal to that of this factory and some // other factory is responsible for it, then this factory should not track the request // because it has no hope of satisfying it. - && (n.score < mScore || n.factorySerialNumber == mSerialNumber) + && (n.score < mScore || n.providerId == mProviderId) // If this factory can't satisfy the capability needs of this request, then it // should not be tracked. && n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter) @@ -373,7 +358,7 @@ public class NetworkFactory extends Handler { // assigned to the factory // - This factory can't satisfy the capability needs of the request // - The concrete implementation of the factory rejects the request - && ((n.score > mScore && n.factorySerialNumber != mSerialNumber) + && ((n.score > mScore && n.providerId != mProviderId) || !n.request.networkCapabilities.satisfiedByNetworkCapabilities( mCapabilityFilter) || !acceptRequest(n.request, n.score)); @@ -408,12 +393,7 @@ public class NetworkFactory extends Handler { protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) { post(() -> { if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r); - Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r); - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(msg); - } else { - mPreConnectedQueue.add(msg); - } + ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(r); }); } @@ -444,8 +424,13 @@ public class NetworkFactory extends Handler { return mNetworkRequests.size(); } + /* TODO: delete when all callers have migrated to NetworkProvider IDs. */ public int getSerialNumber() { - return mSerialNumber; + return mProviderId; + } + + public int getProviderId() { + return mProviderId; } protected void log(String s) { @@ -465,8 +450,8 @@ public class NetworkFactory extends Handler { @Override public String toString() { - StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=") - .append(mSerialNumber).append(", ScoreFilter=") + StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mProviderId=") + .append(mProviderId).append(", ScoreFilter=") .append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=") .append(mNetworkRequests.size()).append(", refCount=").append(mRefCount) .append("}"); diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java deleted file mode 100644 index 4ad52d5aa1bc..000000000000 --- a/core/java/android/net/NetworkMisc.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * A grab-bag of information (metadata, policies, properties, etc) about a - * {@link Network}. Since this contains PII, it should not be sent outside the - * system. - * - * @hide - */ -public class NetworkMisc implements Parcelable { - - /** - * If the {@link Network} is a VPN, whether apps are allowed to bypass the - * VPN. This is set by a {@link VpnService} and used by - * {@link ConnectivityManager} when creating a VPN. - */ - public boolean allowBypass; - - /** - * Set if the network was manually/explicitly connected to by the user either from settings - * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi - * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to - * connect to a particular access point is also explicit, though this may change in the future - * as we want apps to use the multinetwork apis. - */ - public boolean explicitlySelected; - - /** - * Set if the user desires to use this network even if it is unvalidated. This field has meaning - * only if {@link explicitlySelected} is true. If it is, this field must also be set to the - * appropriate value based on previous user choice. - */ - public boolean acceptUnvalidated; - - /** - * Whether the user explicitly set that this network should be validated even if presence of - * only partial internet connectivity. - */ - public boolean acceptPartialConnectivity; - - /** - * Set to avoid surfacing the "Sign in to network" notification. - * if carrier receivers/apps are registered to handle the carrier-specific provisioning - * procedure, a carrier specific provisioning notification will be placed. - * only one notification should be displayed. This field is set based on - * which notification should be used for provisioning. - */ - public boolean provisioningNotificationDisabled; - - /** - * For mobile networks, this is the subscriber ID (such as IMSI). - */ - public String subscriberId; - - /** - * Set to skip 464xlat. This means the device will treat the network as IPv6-only and - * will not attempt to detect a NAT64 via RFC 7050 DNS lookups. - */ - public boolean skip464xlat; - - /** - * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. - * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. - */ - public boolean hasShownBroken; - - public NetworkMisc() { - } - - public NetworkMisc(NetworkMisc nm) { - if (nm != null) { - allowBypass = nm.allowBypass; - explicitlySelected = nm.explicitlySelected; - acceptUnvalidated = nm.acceptUnvalidated; - subscriberId = nm.subscriberId; - provisioningNotificationDisabled = nm.provisioningNotificationDisabled; - skip464xlat = nm.skip464xlat; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(allowBypass ? 1 : 0); - out.writeInt(explicitlySelected ? 1 : 0); - out.writeInt(acceptUnvalidated ? 1 : 0); - out.writeString(subscriberId); - out.writeInt(provisioningNotificationDisabled ? 1 : 0); - out.writeInt(skip464xlat ? 1 : 0); - } - - public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() { - @Override - public NetworkMisc createFromParcel(Parcel in) { - NetworkMisc networkMisc = new NetworkMisc(); - networkMisc.allowBypass = in.readInt() != 0; - networkMisc.explicitlySelected = in.readInt() != 0; - networkMisc.acceptUnvalidated = in.readInt() != 0; - networkMisc.subscriberId = in.readString(); - networkMisc.provisioningNotificationDisabled = in.readInt() != 0; - networkMisc.skip464xlat = in.readInt() != 0; - return networkMisc; - } - - @Override - public NetworkMisc[] newArray(int size) { - return new NetworkMisc[size]; - } - }; -} diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index f6dc52522cb2..428a4ea32b28 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -163,27 +163,26 @@ public class NetworkScoreManager { public static final String EXTRA_NEW_SCORER = "newScorer"; /** @hide */ - @IntDef({CACHE_FILTER_NONE, CACHE_FILTER_CURRENT_NETWORK, CACHE_FILTER_SCAN_RESULTS}) + @IntDef({SCORE_FILTER_NONE, SCORE_FILTER_CURRENT_NETWORK, SCORE_FILTER_SCAN_RESULTS}) @Retention(RetentionPolicy.SOURCE) - public @interface CacheUpdateFilter {} + public @interface ScoreUpdateFilter {} /** - * Do not filter updates sent to the cache. - * @hide + * Do not filter updates sent to the {@link NetworkScoreCallback}]. */ - public static final int CACHE_FILTER_NONE = 0; + public static final int SCORE_FILTER_NONE = 0; /** - * Only send cache updates when the network matches the connected network. - * @hide + * Only send updates to the {@link NetworkScoreCallback} when the network matches the connected + * network. */ - public static final int CACHE_FILTER_CURRENT_NETWORK = 1; + public static final int SCORE_FILTER_CURRENT_NETWORK = 1; /** - * Only send cache updates when the network is part of the current scan result set. - * @hide + * Only send updates to the {@link NetworkScoreCallback} when the network is part of the + * current scan result set. */ - public static final int CACHE_FILTER_SCAN_RESULTS = 2; + public static final int SCORE_FILTER_SCAN_RESULTS = 2; /** @hide */ @IntDef({RECOMMENDATIONS_ENABLED_FORCED_OFF, RECOMMENDATIONS_ENABLED_OFF, @@ -410,7 +409,7 @@ public class NetworkScoreManager { @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) @Deprecated // migrate to registerNetworkScoreCache(int, INetworkScoreCache, int) public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { - registerNetworkScoreCache(networkType, scoreCache, CACHE_FILTER_NONE); + registerNetworkScoreCache(networkType, scoreCache, SCORE_FILTER_NONE); } /** @@ -418,7 +417,7 @@ public class NetworkScoreManager { * * @param networkType the type of network this cache can handle. See {@link NetworkKey#type} * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores - * @param filterType the {@link CacheUpdateFilter} to apply + * @param filterType the {@link ScoreUpdateFilter} to apply * @throws SecurityException if the caller does not hold the * {@link permission#REQUEST_NETWORK_SCORES} permission. * @throws IllegalArgumentException if a score cache is already registered for this type. @@ -426,7 +425,7 @@ public class NetworkScoreManager { */ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache, - @CacheUpdateFilter int filterType) { + @ScoreUpdateFilter int filterType) { try { mService.registerNetworkScoreCache(networkType, scoreCache, filterType); } catch (RemoteException e) { @@ -510,7 +509,7 @@ public class NetworkScoreManager { * Register a network score callback. * * @param networkType the type of network this cache can handle. See {@link NetworkKey#type} - * @param filterType the {@link CacheUpdateFilter} to apply + * @param filterType the {@link ScoreUpdateFilter} to apply * @param callback implementation of {@link NetworkScoreCallback} that will be invoked when the * scores change. * @param executor The executor on which to execute the callbacks. @@ -522,7 +521,7 @@ public class NetworkScoreManager { @SystemApi @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCallback(@NetworkKey.NetworkType int networkType, - @CacheUpdateFilter int filterType, + @ScoreUpdateFilter int filterType, @NonNull @CallbackExecutor Executor executor, @NonNull NetworkScoreCallback callback) throws SecurityException { if (callback == null || executor == null) { diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index b0c2546e0300..ac70b523bcc1 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -631,10 +631,12 @@ public final class BinderProxy implements IBinder { } } - private static void sendDeathNotice(DeathRecipient recipient) { - if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient); + private static void sendDeathNotice(DeathRecipient recipient, IBinder binderProxy) { + if (false) { + Log.v("JavaBinder", "sendDeathNotice to " + recipient + " for " + binderProxy); + } try { - recipient.binderDied(); + recipient.binderDied(binderProxy); } catch (RuntimeException exc) { Log.w("BinderNative", "Uncaught exception from death notification", exc); diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index f336fdab5941..f5fe9c3334bd 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -285,6 +285,13 @@ public interface IBinder { */ public interface DeathRecipient { public void binderDied(); + + /** + * @hide + */ + default void binderDied(IBinder who) { + binderDied(); + } } /** diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java index 348574ed43c7..f4c87ac9dfc9 100644 --- a/core/java/android/os/TimestampedValue.java +++ b/core/java/android/os/TimestampedValue.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import java.util.Objects; @@ -35,19 +36,27 @@ import java.util.Objects; * @param <T> the type of the value with an associated timestamp * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TimestampedValue<T> implements Parcelable { private final long mReferenceTimeMillis; + @Nullable private final T mValue; - public TimestampedValue(long referenceTimeMillis, T value) { + public TimestampedValue(long referenceTimeMillis, @Nullable T value) { mReferenceTimeMillis = referenceTimeMillis; mValue = value; } + /** Returns the reference time value. See {@link TimestampedValue} for more information. */ public long getReferenceTimeMillis() { return mReferenceTimeMillis; } + /** + * Returns the value associated with the timestamp. See {@link TimestampedValue} for more + * information. + */ + @Nullable public T getValue() { return mValue; } @@ -86,6 +95,8 @@ public final class TimestampedValue<T> implements Parcelable { return one.mReferenceTimeMillis - two.mReferenceTimeMillis; } + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR = new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 6e199ce3a73f..d8fadfb41189 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1850,10 +1850,7 @@ public class UserManager { * Checks if the calling app is running in a managed profile. * * @return whether the caller is in a managed profile. - * @hide */ - @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile() { // No need for synchronization. Once it becomes non-null, it'll be non-null forever. // Worst case we might end up calling the AIDL method multiple times but that's fine. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 06805238e6f5..089122ddb023 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -389,6 +389,21 @@ public final class Settings { "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; /** + * Activity Action: Show settings to allow configuration of cross-profile access for apps + * + * Input: Optionally, the Intent's data URI can specify the application package name to + * directly invoke the management GUI specific to the package name. For example + * "package:com.my.app". + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS = + "android.settings.MANAGE_CROSS_PROFILE_ACCESS"; + + /** * Activity Action: Show the "Open by Default" page in a particular application's details page. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard against this. @@ -495,28 +510,33 @@ public final class Settings { "android.settings.WIFI_IP_SETTINGS"; /** - * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code and start + * Activity Action: Show setting page to process a Wi-Fi Easy Connect (aka DPP) URI and start * configuration. This intent should be used when you want to use this device to take on the - * configurator role for an IoT/other device. When provided with a valid DPP URI string Settings - * will open a wifi selection screen for the user to indicate which network they would like to - * configure the device specified in the DPP URI string for and carry them through the rest of - * the flow for provisioning the device. + * configurator role for an IoT/other device. When provided with a valid DPP URI + * string, Settings will open a Wi-Fi selection screen for the user to indicate which network + * they would like to configure the device specified in the DPP URI string and + * carry them through the rest of the flow for provisioning the device. * <p> - * In some cases, a matching Activity may not exist, so ensure you safeguard against this by - * checking WifiManager.isEasyConnectSupported(); + * In some cases, a matching Activity may not exist, so ensure to safeguard against this by + * checking {@link WifiManager#isEasyConnectSupported()}. * <p> * Input: The Intent's data URI specifies bootstrapping information for authenticating and - * provisioning the peer, with the "DPP" scheme. - * <p> - * Output: After {@code startActivityForResult}, the callback {@code onActivityResult} will have - * resultCode {@link android.app.Activity#RESULT_OK} if Wi-Fi Easy Connect configuration succeeded - * and the user tapped 'Done' button, or {@link android.app.Activity#RESULT_CANCELED} if operation - * failed and user tapped 'Cancel'. In case the operation has failed, a status code from {@link - * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode} will be returned as - * Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2 Enrollees report additional - * details about the error they encountered, which will be provided in the {@link - * #EXTRA_EASY_CONNECT_ATTEMPTED_SSID}, {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link - * #EXTRA_EASY_CONNECT_BAND_LIST}. + * provisioning the peer, and uses a "DPP" scheme. The URI should be attached to the intent + * using {@link Intent#setData(Uri)}. The calling app can obtain a DPP URI in any + * way, e.g. by scanning a QR code or other out-of-band methods. The calling app may also + * attach the {@link #EXTRA_EASY_CONNECT_BAND_LIST} extra to provide information + * about the bands supported by the enrollee device. + * <p> + * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback + * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if + * the Wi-Fi Easy Connect configuration succeeded and the user tapped the 'Done' button, or + * {@link android.app.Activity#RESULT_CANCELED} if the operation failed and user tapped the + * 'Cancel' button. In case the operation has failed, a status code from + * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*} will + * be returned as an Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2 + * Enrollees report additional details about the error they encountered, which will be + * provided in the {@link #EXTRA_EASY_CONNECT_ATTEMPTED_SSID}, + * {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link #EXTRA_EASY_CONNECT_BAND_LIST}. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = @@ -525,12 +545,15 @@ public final class Settings { /** * Activity Extra: The Easy Connect operation error code * <p> - * An extra returned on the result intent received when using the {@link - * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This - * extra contains the error code of the operation - one of - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode}. - * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, + * An extra returned on the result intent received when using the + * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. + * This extra contains the integer error code of the operation - one of + * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*}. If + * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, * then this extra is not attached to the result intent. + * <p> + * Use the {@link Intent#hasExtra(String)} to determine whether the extra is attached and + * {@link Intent#getIntExtra(String, int)} to obtain the error code data. */ public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE"; @@ -542,11 +565,13 @@ public final class Settings { * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This * extra contains the SSID of the Access Point that the remote Enrollee tried to connect to. * This value is populated only by remote R2 devices, and only for the following error codes: - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}. + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}. * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then * this extra is not attached to the result intent. + * <p> + * Use the {@link Intent#getStringExtra(String)} to obtain the SSID. */ public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID = "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID"; @@ -556,13 +581,15 @@ public final class Settings { * <p> * An extra returned on the result intent received when using the {@link * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This - * extra contains the list channels the Enrollee used to scan for a network. This value is + * extra contains the channel list that the Enrollee scanned for a network. This value is * populated only by remote R2 devices, and only for the following error code: {@link - * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}. + * android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}. * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then * this extra is not attached to the result intent. The list is JSON formatted, as an array * (Wi-Fi global operating classes) of arrays (Wi-Fi channels). + * <p> + * Use the {@link Intent#getStringExtra(String)} to obtain the list. */ public static final String EXTRA_EASY_CONNECT_CHANNEL_LIST = "android.provider.extra.EASY_CONNECT_CHANNEL_LIST"; @@ -570,17 +597,31 @@ public final class Settings { /** * Activity Extra: The Band List that the Enrollee supports. * <p> - * An extra returned on the result intent received when using the {@link - * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This - * extra contains the bands the Enrollee supports, expressed as the Global Operating Class, - * see Table E-4 in IEEE Std 802.11-2016 -Global operating classes. This value is populated only - * by remote R2 devices, and only for the following error codes: {@link - * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION} - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}. - * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If - * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then - * this extra is not attached to the result intent. + * This extra contains the bands the Enrollee supports, expressed as the Global Operating + * Class, see Table E-4 in IEEE Std 802.11-2016 Global operating classes. It is used both as + * input, to configure the Easy Connect operation and as output of the operation. + * <p> + * As input: an optional extra to be attached to the + * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI}. If attached, it indicates the bands which + * the remote device (enrollee, device-to-be-configured) supports. The Settings operation + * may take this into account when presenting the user with list of networks configurations + * to be used. The calling app may obtain this information in any out-of-band method. The + * information should be attached as an array of raw integers - using the + * {@link Intent#putExtra(String, int[])}. + * <p> + * As output: an extra returned on the result intent received when using the + * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation + * . This value is populated only by remote R2 devices, and only for the following error + * codes: + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}, + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}, + * or + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}. + * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. + * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK} + * , then this extra is not attached to the result intent. + * <p> + * Use the {@link Intent#getIntArrayExtra(String)} to obtain the list. */ public static final String EXTRA_EASY_CONNECT_BAND_LIST = "android.provider.extra.EASY_CONNECT_BAND_LIST"; diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 2e7ac3f505fa..f3690648f35b 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4142,7 +4142,6 @@ public final class Telephony { * <li>{@link #ENABLE_CMAS_PRESIDENTIAL_PREF}</li> * <li>{@link #ENABLE_ALERT_VIBRATION_PREF}</li> * <li>{@link #ENABLE_EMERGENCY_PERF}</li> - * <li>{@link #ENABLE_FULL_VOLUME_PREF}</li> * <li>{@link #ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF}</li> * </ul> * @hide @@ -4205,10 +4204,6 @@ public final class Telephony { public static final @NonNull String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts"; - /** Preference to enable volume for alerts */ - public static final @NonNull String ENABLE_FULL_VOLUME_PREF = - "use_full_volume"; - /** Preference to enable receive alerts in second language */ public static final @NonNull String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF = "receive_cmas_in_second_language"; diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java index d646e23d230a..00060ab8ef4a 100644 --- a/core/java/android/se/omapi/SEService.java +++ b/core/java/android/se/omapi/SEService.java @@ -22,14 +22,11 @@ package android.se.omapi; -import android.app.ActivityThread; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -143,10 +140,6 @@ public final class SEService { throw new NullPointerException("Arguments must not be null"); } - if (!hasOMAPIReaders()) { - throw new UnsupportedOperationException("Device does not support any OMAPI reader"); - } - mContext = context; mSEListener.mListener = listener; mSEListener.mExecutor = executor; @@ -277,23 +270,4 @@ public final class SEService { throw new IllegalStateException(e.getMessage()); } } - - /** - * Helper to check if this device support any OMAPI readers - */ - private static boolean hasOMAPIReaders() { - IPackageManager pm = ActivityThread.getPackageManager(); - if (pm == null) { - Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported"); - return true; - } - try { - return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0) - || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0) - || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0); - } catch (RemoteException e) { - Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e); - return true; - } - } } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index d827b30f27b6..262d9896df87 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -21,6 +21,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.IntentSender; import android.os.Parcel; import android.os.Parcelable; @@ -238,6 +239,7 @@ public final class Dataset implements Parcelable { public Builder(@NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) { Preconditions.checkNotNull(presentation, "presentation must be non-null"); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); mPresentation = presentation; mInlinePresentation = inlinePresentation; } @@ -248,7 +250,8 @@ public final class Dataset implements Parcelable { * @param presentation The presentation used to visualize this dataset. */ public Builder(@NonNull RemoteViews presentation) { - this(presentation, null); + Preconditions.checkNotNull(presentation, "presentation must be non-null"); + mPresentation = presentation; } /** @@ -262,7 +265,9 @@ public final class Dataset implements Parcelable { * @hide */ @SystemApi + @TestApi public Builder(@NonNull InlinePresentation inlinePresentation) { + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); mInlinePresentation = inlinePresentation; } @@ -576,6 +581,7 @@ public final class Dataset implements Parcelable { * @hide */ @SystemApi + @TestApi public @NonNull Builder setInlinePresentation(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull InlinePresentation inlinePresentation) { @@ -672,11 +678,13 @@ public final class Dataset implements Parcelable { // using specially crafted parcels. final RemoteViews presentation = parcel.readParcelable(null); final InlinePresentation inlinePresentation = parcel.readParcelable(null); - final Builder builder = presentation == null - ? new Builder(inlinePresentation) - : inlinePresentation == null + final Builder builder = presentation != null + ? inlinePresentation == null ? new Builder(presentation) - : new Builder(presentation, inlinePresentation); + : new Builder(presentation, inlinePresentation) + : inlinePresentation == null + ? new Builder() + : new Builder(inlinePresentation); final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR); final ArrayList<AutofillValue> values = diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 5977bafd7cf1..4ead3fc67260 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -49,6 +49,9 @@ oneway interface INotificationListener void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); void onNotificationsSeen(in List<String> keys); + void onPanelRevealed(int items); + void onPanelHidden(); + void onNotificationVisibilityChanged(String key, boolean isVisible); void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); void onNotificationDirectReply(String key); void onSuggestedReplySent(String key, in CharSequence reply, int source); diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index da4025419656..e976e18602c1 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -182,6 +182,32 @@ public abstract class NotificationAssistantService extends NotificationListenerS } /** + * Implement this to know when the notification panel is revealed + * + * @param items Number of items on the panel at time of opening + */ + public void onPanelRevealed(int items) { + + } + + /** + * Implement this to know when the notification panel is hidden + */ + public void onPanelHidden() { + + } + + /** + * Implement this to know when a notification becomes visible or hidden from the user. + * + * @param key the notification key + * @param isVisible whether the notification is visible. + */ + public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) { + + } + + /** * Implement this to know when a notification change (expanded / collapsed) is visible to user. * * @param key the notification key @@ -337,6 +363,30 @@ public abstract class NotificationAssistantService extends NotificationListenerS } @Override + public void onPanelRevealed(int items) { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = items; + mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED, + args).sendToTarget(); + } + + @Override + public void onPanelHidden() { + SomeArgs args = SomeArgs.obtain(); + mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN, + args).sendToTarget(); + } + + @Override + public void onNotificationVisibilityChanged(String key, boolean isVisible) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = key; + args.argi1 = isVisible ? 1 : 0; + mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED, + args).sendToTarget(); + } + + @Override public void onNotificationExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { SomeArgs args = SomeArgs.obtain(); @@ -394,6 +444,9 @@ public abstract class NotificationAssistantService extends NotificationListenerS public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6; public static final int MSG_ON_ACTION_INVOKED = 7; public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8; + public static final int MSG_ON_PANEL_REVEALED = 9; + public static final int MSG_ON_PANEL_HIDDEN = 10; + public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11; public MyHandler(Looper looper) { super(looper, null, false); @@ -480,6 +533,25 @@ public abstract class NotificationAssistantService extends NotificationListenerS onAllowedAdjustmentsChanged(); break; } + case MSG_ON_PANEL_REVEALED: { + SomeArgs args = (SomeArgs) msg.obj; + int items = args.argi1; + args.recycle(); + onPanelRevealed(items); + break; + } + case MSG_ON_PANEL_HIDDEN: { + onPanelHidden(); + break; + } + case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + String key = (String) args.arg1; + boolean isVisible = args.argi1 == 1; + args.recycle(); + onNotificationVisibilityChanged(key, isVisible); + break; + } } } } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 80d054b700f8..fd04f499a432 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1383,6 +1383,22 @@ public abstract class NotificationListenerService extends Service { } @Override + public void onPanelRevealed(int items) throws RemoteException { + // no-op in the listener + } + + @Override + public void onPanelHidden() throws RemoteException { + // no-op in the listener + } + + @Override + public void onNotificationVisibilityChanged( + String key, boolean isVisible) { + // no-op in the listener + } + + @Override public void onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId) throws RemoteException { diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 67925bfea2c2..0f339988ba3e 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -119,7 +119,9 @@ public class AlwaysOnHotwordDetector { @IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = { RECOGNITION_FLAG_NONE, RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO, - RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS + RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS, + RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION, + RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, }) public @interface RecognitionFlags {} @@ -144,6 +146,26 @@ public class AlwaysOnHotwordDetector { */ public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2; + /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use AEC. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for + * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the + * audio capability supported, there will be no audio effect applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4; + + /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use noise suppression. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for + * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the + * audio capability supported, there will be no audio effect applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; + //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----// // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags. @@ -168,6 +190,46 @@ public class AlwaysOnHotwordDetector { public static final int RECOGNITION_MODE_USER_IDENTIFICATION = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; + //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --// + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { + AUDIO_CAPABILITY_ECHO_CANCELLATION, + AUDIO_CAPABILITY_NOISE_SUPPRESSION, + }) + public @interface AudioCapabilities {} + + /** + * If set the underlying module supports AEC. + * Returned by {@link #getSupportedAudioCapabilities()} + */ + public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = + SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + + /** + * If set, the underlying module supports noise suppression. + * Returned by {@link #getSupportedAudioCapabilities()} + */ + public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = + SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = { + MODEL_PARAM_THRESHOLD_FACTOR, + }) + public @interface ModelParams {} + + /** + * Controls the sensitivity threshold adjustment factor for a given model. + * Negative value corresponds to less sensitive model (high threshold) and + * a positive value corresponds to a more sensitive model (low threshold). + * Default value is 0. + */ + public static final int MODEL_PARAM_THRESHOLD_FACTOR = + android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR; + static final String TAG = "AlwaysOnHotwordDetector"; static final boolean DBG = false; @@ -198,6 +260,53 @@ public class AlwaysOnHotwordDetector { private int mAvailability = STATE_NOT_READY; /** + * A ModelParamRange is a representation of supported parameter range for a + * given loaded model. + */ + public static final class ModelParamRange { + private final SoundTrigger.ModelParamRange mModelParamRange; + + /** @hide */ + ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) { + mModelParamRange = modelParamRange; + } + + /** + * The inclusive start of supported range. + * + * @return start of range + */ + public int start() { + return mModelParamRange.start; + } + + /** + * The inclusive end of supported range. + * + * @return end of range + */ + public int end() { + return mModelParamRange.end; + } + + @Override + @NonNull + public String toString() { + return mModelParamRange.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return mModelParamRange.equals(obj); + } + + @Override + public int hashCode() { + return mModelParamRange.hashCode(); + } + } + + /** * Additional payload for {@link Callback#onDetected}. */ public static class EventPayload { @@ -385,6 +494,37 @@ public class AlwaysOnHotwordDetector { } /** + * Get the audio capabilities supported by the platform which can be enabled when + * starting a recognition. + * + * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION + * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION + * + * @return Bit field encoding of the AudioCapabilities supported. + */ + @AudioCapabilities + public int getSupportedAudioCapabilities() { + if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()"); + synchronized (mLock) { + return getSupportedAudioCapabilitiesLocked(); + } + } + + private int getSupportedAudioCapabilitiesLocked() { + try { + ModuleProperties properties = + mModelManagementService.getDspModuleProperties(mVoiceInteractionService); + if (properties != null) { + return properties.audioCapabilities; + } + + return 0; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Starts recognition for the associated keyphrase. * * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO @@ -445,6 +585,83 @@ public class AlwaysOnHotwordDetector { } /** + * Set a model specific {@link ModelParams} with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this + * method. + * + * @param modelParam {@link ModelParams} + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + public int setParameter(@ModelParams int modelParam, int value) { + if (DBG) { + Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("setParameter called on an invalid detector"); + } + + return setParameterLocked(modelParam, value); + } + } + + /** + * Get a model specific {@link ModelParams}. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See {@link ModelParams} for parameter default values. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before + * calling this method. + * + * @param modelParam {@link ModelParams} + * @return value of parameter + */ + public int getParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "getParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("getParameter called on an invalid detector"); + } + + return getParameterLocked(modelParam); + } + } + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter} + * or {@link AlwaysOnHotwordDetector#getParameter}. + * + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public ModelParamRange queryParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "queryParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("queryParameter called on an invalid detector"); + } + + return queryParameterLocked(modelParam); + } + } + + /** * Creates an intent to start the enrollment for the associated keyphrase. * This intent must be invoked using {@link Context#startForegroundService(Intent)}. * Starting re-enrollment is only valid if the keyphrase is un-enrolled, @@ -571,12 +788,21 @@ public class AlwaysOnHotwordDetector { (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; boolean allowMultipleTriggers = (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; + + int audioCapabilities = 0; + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { + audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION; + } + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) { + audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION; + } + int code = STATUS_ERROR; try { code = mModelManagementService.startRecognition(mVoiceInteractionService, mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback, new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, - recognitionExtra, null /* additional data */)); + recognitionExtra, null /* additional data */, audioCapabilities)); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in startRecognition!", e); } @@ -601,6 +827,47 @@ public class AlwaysOnHotwordDetector { return code; } + private int setParameterLocked(@ModelParams int modelParam, int value) { + try { + int code = mModelManagementService.setParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam, value); + + if (code != STATUS_OK) { + Slog.w(TAG, "setParameter failed with error code " + code); + } + + return code; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private int getParameterLocked(@ModelParams int modelParam) { + try { + return mModelManagementService.getParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@ModelParams int modelParam) { + try { + SoundTrigger.ModelParamRange modelParamRange = + mModelManagementService.queryParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + + if (modelParamRange == null) { + return null; + } + + return new ModelParamRange(modelParamRange); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void notifyStateChangedLocked() { Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); message.arg1 = mAvailability; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 1b2db36c1335..9d22d304ad00 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -42,6 +42,8 @@ public class FeatureFlagUtils { public static final String DYNAMIC_SYSTEM = "settings_dynamic_system"; public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2"; public static final String SETTINGS_FUSE_FLAG = "settings_fuse"; + public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = + "settings_notif_convo_bypass_shortcut_req"; private static final Map<String, String> DEFAULT_FLAGS; @@ -60,6 +62,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_work_profile", "true"); DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false"); DEFAULT_FLAGS.put("settings_conditionals", "false"); + DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); } /** diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index fa994ba76fd7..0892c94d5bec 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -16,6 +16,8 @@ package android.util; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -25,172 +27,270 @@ import android.net.Network; import android.net.NetworkInfo; import android.net.SntpClient; import android.os.SystemClock; -import android.os.TimestampedValue; import android.provider.Settings; import android.text.TextUtils; +import com.android.internal.annotations.GuardedBy; + +import java.util.Objects; +import java.util.function.Supplier; + /** - * {@link TrustedTime} that connects with a remote NTP server as its trusted - * time source. + * A singleton that connects with a remote NTP server as its trusted time source. This class + * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the + * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()} + * will block during that request. * * @hide */ public class NtpTrustedTime implements TrustedTime { + + /** + * The result of a successful NTP query. + * + * @hide + */ + public static class TimeResult { + private final long mTimeMillis; + private final long mElapsedRealtimeMillis; + private final long mCertaintyMillis; + + public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) { + mTimeMillis = timeMillis; + mElapsedRealtimeMillis = elapsedRealtimeMillis; + mCertaintyMillis = certaintyMillis; + } + + public long getTimeMillis() { + return mTimeMillis; + } + + public long getElapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + public long getCertaintyMillis() { + return mCertaintyMillis; + } + + /** Calculates and returns the current time accounting for the age of this result. */ + public long currentTimeMillis() { + return mTimeMillis + getAgeMillis(); + } + + /** Calculates and returns the age of this result. */ + public long getAgeMillis() { + return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis; + } + + @Override + public String toString() { + return "TimeResult{" + + "mTimeMillis=" + mTimeMillis + + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis + + ", mCertaintyMillis=" + mCertaintyMillis + + '}'; + } + } + private static final String TAG = "NtpTrustedTime"; private static final boolean LOGD = false; private static NtpTrustedTime sSingleton; - private static Context sContext; - private final String mServer; - private final long mTimeout; + @NonNull + private final Context mContext; + + /** + * A supplier that returns the ConnectivityManager. The Supplier can return null if + * ConnectivityService isn't running yet. + */ + private final Supplier<ConnectivityManager> mConnectivityManagerSupplier = + new Supplier<ConnectivityManager>() { + private ConnectivityManager mConnectivityManager; - private ConnectivityManager mCM; + @Nullable + @Override + public synchronized ConnectivityManager get() { + // We can't do this at initialization time: ConnectivityService might not be running + // yet. + if (mConnectivityManager == null) { + mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); + } + return mConnectivityManager; + } + }; - private boolean mHasCache; - private long mCachedNtpTime; - private long mCachedNtpElapsedRealtime; - private long mCachedNtpCertainty; + // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during + // forceRefresh(). + private volatile TimeResult mTimeResult; - private NtpTrustedTime(String server, long timeout) { - if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server); - mServer = server; - mTimeout = timeout; + private NtpTrustedTime(Context context) { + mContext = Objects.requireNonNull(context); } @UnsupportedAppUsage public static synchronized NtpTrustedTime getInstance(Context context) { if (sSingleton == null) { - final Resources res = context.getResources(); - final ContentResolver resolver = context.getContentResolver(); - - final String defaultServer = res.getString( - com.android.internal.R.string.config_ntpServer); - final long defaultTimeout = res.getInteger( - com.android.internal.R.integer.config_ntpTimeout); - - final String secureServer = Settings.Global.getString( - resolver, Settings.Global.NTP_SERVER); - final long timeout = Settings.Global.getLong( - resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout); - - final String server = secureServer != null ? secureServer : defaultServer; - sSingleton = new NtpTrustedTime(server, timeout); - sContext = context; + Context appContext = context.getApplicationContext(); + sSingleton = new NtpTrustedTime(appContext); } - return sSingleton; } - @Override @UnsupportedAppUsage public boolean forceRefresh() { - // We can't do this at initialization time: ConnectivityService might not be running yet. synchronized (this) { - if (mCM == null) { - mCM = sContext.getSystemService(ConnectivityManager.class); + NtpConnectionInfo connectionInfo = getNtpConnectionInfo(); + if (connectionInfo == null) { + // missing server config, so no trusted time available + if (LOGD) Log.d(TAG, "forceRefresh: invalid server config"); + return false; } - } - final Network network = mCM == null ? null : mCM.getActiveNetwork(); - return forceRefresh(network); - } - - public boolean forceRefresh(Network network) { - if (TextUtils.isEmpty(mServer)) { - // missing server, so no trusted time available - return false; - } - - // We can't do this at initialization time: ConnectivityService might not be running yet. - synchronized (this) { - if (mCM == null) { - mCM = sContext.getSystemService(ConnectivityManager.class); + ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get(); + if (connectivityManager == null) { + if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager"); + return false; + } + final Network network = connectivityManager.getActiveNetwork(); + final NetworkInfo ni = connectivityManager.getNetworkInfo(network); + if (ni == null || !ni.isConnected()) { + if (LOGD) Log.d(TAG, "forceRefresh: no connectivity"); + return false; } - } - - final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network); - if (ni == null || !ni.isConnected()) { - if (LOGD) Log.d(TAG, "forceRefresh: no connectivity"); - return false; - } - - if (LOGD) Log.d(TAG, "forceRefresh() from cache miss"); - final SntpClient client = new SntpClient(); - if (client.requestTime(mServer, (int) mTimeout, network)) { - mHasCache = true; - mCachedNtpTime = client.getNtpTime(); - mCachedNtpElapsedRealtime = client.getNtpTimeReference(); - mCachedNtpCertainty = client.getRoundTripTime() / 2; - return true; - } else { - return false; + if (LOGD) Log.d(TAG, "forceRefresh() from cache miss"); + final SntpClient client = new SntpClient(); + final String serverName = connectionInfo.getServer(); + final int timeoutMillis = connectionInfo.getTimeoutMillis(); + if (client.requestTime(serverName, timeoutMillis, network)) { + long ntpCertainty = client.getRoundTripTime() / 2; + mTimeResult = new TimeResult( + client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty); + return true; + } else { + return false; + } } } - @Override + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public boolean hasCache() { - return mHasCache; + return mTimeResult != null; } + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @Override public long getCacheAge() { - if (mHasCache) { - return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime; + TimeResult timeResult = mTimeResult; + if (timeResult != null) { + return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis(); } else { return Long.MAX_VALUE; } } - @Override - public long getCacheCertainty() { - if (mHasCache) { - return mCachedNtpCertainty; - } else { - return Long.MAX_VALUE; - } - } - - @Override + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public long currentTimeMillis() { - if (!mHasCache) { + TimeResult timeResult = mTimeResult; + if (timeResult == null) { throw new IllegalStateException("Missing authoritative time source"); } if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit"); // current time is age after the last ntp cache; callers who - // want fresh values will hit makeAuthoritative() first. - return mCachedNtpTime + getCacheAge(); + // want fresh values will hit forceRefresh() first. + return timeResult.currentTimeMillis(); } + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public long getCachedNtpTime() { if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit"); - return mCachedNtpTime; + TimeResult timeResult = mTimeResult; + return timeResult == null ? 0 : timeResult.getTimeMillis(); } + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public long getCachedNtpTimeReference() { - return mCachedNtpElapsedRealtime; + TimeResult timeResult = mTimeResult; + return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis(); } /** - * Returns the combination of {@link #getCachedNtpTime()} and {@link - * #getCachedNtpTimeReference()} as a {@link TimestampedValue}. This method is useful when - * passing the time to another component that will adjust for elapsed time. - * - * @throws IllegalStateException if there is no cached value + * Returns an object containing the latest NTP information available. Can return {@code null} if + * no information is available. */ - public TimestampedValue<Long> getCachedNtpTimeSignal() { - if (!mHasCache) { - throw new IllegalStateException("Missing authoritative time source"); + @Nullable + public TimeResult getCachedTimeResult() { + return mTimeResult; + } + + private static class NtpConnectionInfo { + + @NonNull private final String mServer; + private final int mTimeoutMillis; + + NtpConnectionInfo(@NonNull String server, int timeoutMillis) { + mServer = Objects.requireNonNull(server); + mTimeoutMillis = timeoutMillis; } - if (LOGD) Log.d(TAG, "getCachedNtpTimeSignal() cache hit"); - return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime); + @NonNull + public String getServer() { + return mServer; + } + + int getTimeoutMillis() { + return mTimeoutMillis; + } } + @GuardedBy("this") + private NtpConnectionInfo getNtpConnectionInfo() { + final ContentResolver resolver = mContext.getContentResolver(); + + final Resources res = mContext.getResources(); + final String defaultServer = res.getString( + com.android.internal.R.string.config_ntpServer); + final int defaultTimeoutMillis = res.getInteger( + com.android.internal.R.integer.config_ntpTimeout); + + final String secureServer = Settings.Global.getString( + resolver, Settings.Global.NTP_SERVER); + final int timeoutMillis = Settings.Global.getInt( + resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis); + + final String server = secureServer != null ? secureServer : defaultServer; + return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis); + } } diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java index 1360f874507f..f41fe85fa8bb 100644 --- a/core/java/android/util/TrustedTime.java +++ b/core/java/android/util/TrustedTime.java @@ -20,42 +20,48 @@ import android.compat.annotation.UnsupportedAppUsage; /** * Interface that provides trusted time information, possibly coming from an NTP - * server. Implementations may cache answers until {@link #forceRefresh()}. + * server. * * @hide + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ public interface TrustedTime { /** * Force update with an external trusted time source, returning {@code true} * when successful. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage public boolean forceRefresh(); /** * Check if this instance has cached a response from a trusted time source. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage - public boolean hasCache(); + boolean hasCache(); /** * Return time since last trusted time source contact, or * {@link Long#MAX_VALUE} if never contacted. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage public long getCacheAge(); /** - * Return certainty of cached trusted time in milliseconds, or - * {@link Long#MAX_VALUE} if never contacted. Smaller values are more - * precise. - */ - public long getCacheCertainty(); - - /** * Return current time similar to {@link System#currentTimeMillis()}, * possibly using a cached authoritative time source. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage - public long currentTimeMillis(); + long currentTimeMillis(); } diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl new file mode 100644 index 000000000000..e92aafed6f22 --- /dev/null +++ b/core/java/android/view/ITaskOrganizer.aidl @@ -0,0 +1,38 @@ +/* //device/java/android/android/view/ITaskOrganizer.aidl +** +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.view; + +import android.view.IWindowContainer; +import android.view.SurfaceControl; +import android.app.ActivityManager; + +/** + * Interface for ActivityTaskManager/WindowManager to delegate control of tasks. + * {@hide} + */ +oneway interface ITaskOrganizer { + void taskAppeared(in IWindowContainer container, + in ActivityManager.RunningTaskInfo taskInfo); + void taskVanished(in IWindowContainer container); + + /** + * Called upon completion of + * ActivityTaskManagerService#applyTaskOrganizerTransaction + */ + void transactionReady(int id, in SurfaceControl.Transaction t); +}
\ No newline at end of file diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 0207abdda355..775490c757d4 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -42,6 +42,7 @@ import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -299,6 +300,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** + * @see InsetsState#calculateVisibleInsets(Rect, Rect, int) + */ + public Rect calculateVisibleInsets(Rect legacyVisibleInsets, + @SoftInputModeFlags int softInputMode) { + return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode); + } + + /** * Called when the server has dispatched us a new set of inset controls. */ public void onControlsChanged(InsetsSourceControl[] activeControls) { diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 324d562bfcfb..67ccfd6707f4 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; @@ -23,6 +24,7 @@ import android.os.Parcelable; import android.view.InsetsState.InternalInsetsType; import java.io.PrintWriter; +import java.util.Objects; /** * Represents the state of a single window generating insets for clients. @@ -34,6 +36,7 @@ public class InsetsSource implements Parcelable { /** Frame of the source in screen coordinate space */ private final Rect mFrame; + private @Nullable Rect mVisibleFrame; private boolean mVisible; private final Rect mTmpFrame = new Rect(); @@ -54,6 +57,10 @@ public class InsetsSource implements Parcelable { mFrame.set(frame); } + public void setVisibleFrame(@Nullable Rect visibleFrame) { + mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame; + } + public void setVisible(boolean visible) { mVisible = visible; } @@ -66,6 +73,10 @@ public class InsetsSource implements Parcelable { return mFrame; } + public @Nullable Rect getVisibleFrame() { + return mVisibleFrame; + } + public boolean isVisible() { return mVisible; } @@ -79,10 +90,22 @@ public class InsetsSource implements Parcelable { * source. */ public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { + return calculateInsets(relativeFrame, mFrame, ignoreVisibility); + } + + /** + * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets. + */ + public Insets calculateVisibleInsets(Rect relativeFrame) { + return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame, + false /* ignoreVisibility */); + } + + private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) { if (!ignoreVisibility && !mVisible) { return Insets.NONE; } - if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) { + if (!mTmpFrame.setIntersect(frame, relativeFrame)) { return Insets.NONE; } @@ -110,6 +133,9 @@ public class InsetsSource implements Parcelable { pw.print(prefix); pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType)); pw.print(" frame="); pw.print(mFrame.toShortString()); + if (mVisibleFrame != null) { + pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString()); + } pw.print(" visible="); pw.print(mVisible); pw.println(); } @@ -123,6 +149,7 @@ public class InsetsSource implements Parcelable { if (mType != that.mType) return false; if (mVisible != that.mVisible) return false; + if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; return mFrame.equals(that.mFrame); } @@ -137,6 +164,7 @@ public class InsetsSource implements Parcelable { public InsetsSource(Parcel in) { mType = in.readInt(); mFrame = in.readParcelable(null /* loader */); + mVisibleFrame = in.readParcelable(null /* loader */); mVisible = in.readBoolean(); } @@ -149,6 +177,7 @@ public class InsetsSource implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeParcelable(mFrame, 0 /* flags*/); + dest.writeParcelable(mVisibleFrame, 0 /* flags */); dest.writeBoolean(mVisible); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index ae1e579da8f6..e33ca70c222e 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -19,12 +19,14 @@ package android.view; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.ViewRootImpl.sNewInsetsMode; import static android.view.WindowInsets.Type.IME; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.indexOf; +import static android.view.WindowInsets.Type.isVisibleInsetsType; import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; @@ -41,6 +43,7 @@ import android.util.SparseIntArray; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -186,6 +189,32 @@ public class InsetsState implements Parcelable { : systemBars()); } + public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets, + @SoftInputModeFlags int softInputMode) { + if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { + return legacyVisibleInsets; + } + + Insets insets = Insets.NONE; + for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { + InsetsSource source = mSources.get(type); + if (source == null) { + continue; + } + if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) { + continue; + } + + // Ignore everything that's not a system bar or IME. + int publicType = InsetsState.toPublicType(type); + if (!isVisibleInsetsType(publicType, softInputMode)) { + continue; + } + insets = Insets.max(source.calculateVisibleInsets(frame), insets); + } + return insets.toRect(); + } + private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap, @Nullable boolean[] typeVisibilityMap) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ab89ef46e09e..e0f6e0668f8b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -826,6 +826,10 @@ public final class ViewRootImpl implements ViewParent, if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } + if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + mWindowAttributes.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; + } attrs = mWindowAttributes; setTag(); @@ -1308,7 +1312,7 @@ public final class ViewRootImpl implements ViewParent, mWindowAttributes.privateFlags |= compatibleWindowFlag; if (WindowManagerGlobal.USE_BLAST_ADAPTER) { - mWindowAttributes.privateFlags = + mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -2103,6 +2107,12 @@ public final class ViewRootImpl implements ViewParent, Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + private void updateVisibleInsets() { + Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets, + mWindowAttributes.softInputMode); + mAttachInfo.mVisibleInsets.set(visibleInsets); + } + InsetsController getInsetsController() { return mInsetsController; } @@ -2251,7 +2261,7 @@ public final class ViewRootImpl implements ViewParent, insetsChanged = true; } if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { - mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + updateVisibleInsets(); if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } @@ -2317,6 +2327,7 @@ public final class ViewRootImpl implements ViewParent, if (mApplyInsetsRequested) { mApplyInsetsRequested = false; + updateVisibleInsets(); dispatchApplyInsets(host); if (mLayoutRequested) { // Short-circuit catching a new layout request here, so @@ -2501,7 +2512,7 @@ public final class ViewRootImpl implements ViewParent, contentInsetsChanged = true; } if (visibleInsetsChanged) { - mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + updateVisibleInsets(); if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java index 607a87047733..253794f70ef2 100644 --- a/core/java/android/view/WindowContainerTransaction.java +++ b/core/java/android/view/WindowContainerTransaction.java @@ -62,6 +62,18 @@ public class WindowContainerTransaction implements Parcelable { return this; } + /** + * Notify activies within the hiearchy of a container that they have entered picture-in-picture + * mode with the given bounds. + */ + public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container, + Rect bounds) { + Change chg = getOrCreateChange(container.asBinder()); + chg.mSchedulePipCallback = true; + chg.mPinnedBounds = new Rect(bounds); + return this; + } + public Map<IBinder, Change> getChanges() { return mChanges; } @@ -104,12 +116,20 @@ public class WindowContainerTransaction implements Parcelable { private @ActivityInfo.Config int mConfigSetMask = 0; private @WindowConfiguration.WindowConfig int mWindowSetMask = 0; + private boolean mSchedulePipCallback = false; + private Rect mPinnedBounds = null; + public Change() {} protected Change(Parcel in) { mConfiguration.readFromParcel(in); mConfigSetMask = in.readInt(); mWindowSetMask = in.readInt(); + mSchedulePipCallback = (in.readInt() != 0); + if (mSchedulePipCallback ) { + mPinnedBounds = new Rect(); + mPinnedBounds.readFromParcel(in); + } } public Configuration getConfiguration() { @@ -126,6 +146,14 @@ public class WindowContainerTransaction implements Parcelable { return mWindowSetMask; } + /** + * Returns the bounds to be used for scheduling the enter pip callback + * or null if no callback is to be scheduled. + */ + public Rect getEnterPipBounds() { + return mPinnedBounds; + } + @Override public String toString() { final boolean changesBounds = @@ -151,6 +179,11 @@ public class WindowContainerTransaction implements Parcelable { mConfiguration.writeToParcel(dest, flags); dest.writeInt(mConfigSetMask); dest.writeInt(mWindowSetMask); + + dest.writeInt(mSchedulePipCallback ? 1 : 0); + if (mSchedulePipCallback ) { + mPinnedBounds.writeToParcel(dest, flags); + } } @Override diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 9df131de6754..9291b5652425 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -29,6 +29,8 @@ import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.indexOf; import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import android.annotation.IntDef; import android.annotation.IntRange; @@ -40,6 +42,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethod; @@ -1289,6 +1292,17 @@ public final class WindowInsets { public static @InsetsType int all() { return 0xFFFFFFFF; } + + /** + * Checks whether the specified type is considered to be part of visible insets. + * @hide + */ + public static boolean isVisibleInsetsType(int type, + @SoftInputModeFlags int softInputModeFlags) { + int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST; + return (type & Type.systemBars()) != 0 + || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0); + } } /** diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 386c9cb9d14f..860ce90d5fc0 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -17,6 +17,8 @@ package android.view.inputmethod; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; import android.os.Parcelable; import android.view.inline.InlinePresentationSpec; @@ -49,6 +51,21 @@ public final class InlineSuggestionsRequest implements Parcelable { */ private final @NonNull List<InlinePresentationSpec> mPresentationSpecs; + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + private @NonNull String mHostPackageName; + + /** + * @hide + * @see {@link #mHostPackageName}. + */ + public void setHostPackageName(@NonNull String hostPackageName) { + mHostPackageName = hostPackageName; + } + private void onConstructed() { Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size()); } @@ -57,9 +74,15 @@ public final class InlineSuggestionsRequest implements Parcelable { return SUGGESTION_COUNT_UNLIMITED; } + private static String defaultHostPackageName() { + return ActivityThread.currentPackageName(); + } + /** @hide */ abstract static class BaseBuilder { abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value); + + abstract Builder setHostPackageName(@Nullable String value); } @@ -80,11 +103,15 @@ public final class InlineSuggestionsRequest implements Parcelable { @DataClass.Generated.Member /* package-private */ InlineSuggestionsRequest( int maxSuggestionCount, - @NonNull List<InlinePresentationSpec> presentationSpecs) { + @NonNull List<InlinePresentationSpec> presentationSpecs, + @NonNull String hostPackageName) { this.mMaxSuggestionCount = maxSuggestionCount; this.mPresentationSpecs = presentationSpecs; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPresentationSpecs); + this.mHostPackageName = hostPackageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostPackageName); onConstructed(); } @@ -108,6 +135,16 @@ public final class InlineSuggestionsRequest implements Parcelable { return mPresentationSpecs; } + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + @DataClass.Generated.Member + public @NonNull String getHostPackageName() { + return mHostPackageName; + } + @Override @DataClass.Generated.Member public String toString() { @@ -116,13 +153,14 @@ public final class InlineSuggestionsRequest implements Parcelable { return "InlineSuggestionsRequest { " + "maxSuggestionCount = " + mMaxSuggestionCount + ", " + - "presentationSpecs = " + mPresentationSpecs + + "presentationSpecs = " + mPresentationSpecs + ", " + + "hostPackageName = " + mHostPackageName + " }"; } @Override @DataClass.Generated.Member - public boolean equals(@android.annotation.Nullable Object o) { + public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(InlineSuggestionsRequest other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } @@ -134,7 +172,8 @@ public final class InlineSuggestionsRequest implements Parcelable { //noinspection PointlessBooleanExpression return true && mMaxSuggestionCount == that.mMaxSuggestionCount - && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs); + && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs) + && java.util.Objects.equals(mHostPackageName, that.mHostPackageName); } @Override @@ -146,6 +185,7 @@ public final class InlineSuggestionsRequest implements Parcelable { int _hash = 1; _hash = 31 * _hash + mMaxSuggestionCount; _hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs); + _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName); return _hash; } @@ -157,6 +197,7 @@ public final class InlineSuggestionsRequest implements Parcelable { dest.writeInt(mMaxSuggestionCount); dest.writeParcelableList(mPresentationSpecs, flags); + dest.writeString(mHostPackageName); } @Override @@ -173,11 +214,15 @@ public final class InlineSuggestionsRequest implements Parcelable { int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> presentationSpecs = new ArrayList<>(); in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader()); + String hostPackageName = in.readString(); this.mMaxSuggestionCount = maxSuggestionCount; this.mPresentationSpecs = presentationSpecs; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPresentationSpecs); + this.mHostPackageName = hostPackageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostPackageName); onConstructed(); } @@ -205,6 +250,7 @@ public final class InlineSuggestionsRequest implements Parcelable { private int mMaxSuggestionCount; private @NonNull List<InlinePresentationSpec> mPresentationSpecs; + private @NonNull String mHostPackageName; private long mBuilderFieldsSet = 0L; @@ -260,22 +306,40 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + @DataClass.Generated.Member + @Override + @NonNull Builder setHostPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mHostPackageName = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used + mBuilderFieldsSet |= 0x8; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); } + if ((mBuilderFieldsSet & 0x4) == 0) { + mHostPackageName = defaultHostPackageName(); + } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, - mPresentationSpecs); + mPresentationSpecs, + mHostPackageName); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { + if ((mBuilderFieldsSet & 0x8) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -283,10 +347,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1576637222199L, + time = 1578948035951L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic void setHostPackageName(java.lang.String)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 20af76b0d5ca..f851e10fa4f6 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -5737,7 +5737,7 @@ public class Editor { private boolean mIsDraggingCursor; public void onTouchEvent(MotionEvent event) { - if (getSelectionController().isCursorBeingModified()) { + if (hasSelectionController() && getSelectionController().isCursorBeingModified()) { return; } switch (event.getActionMasked()) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index d86766ebdf58..01a0e9b15463 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -565,7 +565,8 @@ public class RemoteViews implements Parcelable, Filter { } private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { - if (icon != null && icon.getType() == Icon.TYPE_URI) { + if (icon != null && (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { visitor.accept(icon.getUri()); } } diff --git a/core/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java index c45a3a4b8b14..2bb3f1fed927 100644 --- a/core/java/com/android/ims/internal/uce/common/CapInfo.java +++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java @@ -64,6 +64,20 @@ public class CapInfo implements Parcelable { private boolean mRcsIpVideoCallSupported = false; /** RCS IP Video call support . */ private boolean mRcsIpVideoOnlyCallSupported = false; + /** IP Geo location Push using SMS. */ + private boolean mGeoSmsSupported = false; + /** RCS call composer support. */ + private boolean mCallComposerSupported = false; + /** RCS post-call support. */ + private boolean mPostCallSupported = false; + /** Shared map support. */ + private boolean mSharedMapSupported = false; + /** Shared Sketch supported. */ + private boolean mSharedSketchSupported = false; + /** Chatbot communication support. */ + private boolean mChatbotSupported = false; + /** Chatbot role support. */ + private boolean mChatbotRoleSupported = false; /** List of supported extensions. */ private String[] mExts = new String[10]; /** Time used to compute when to query again. */ @@ -386,6 +400,104 @@ public class CapInfo implements Parcelable { this.mRcsIpVideoOnlyCallSupported = rcsIpVideoOnlyCallSupported; } + /** + * Checks whether Geo Push via SMS is supported. + */ + public boolean isGeoSmsSupported() { + return mGeoSmsSupported; + } + + /** + * Sets Geolocation Push via SMS as supported or not supported. + */ + public void setGeoSmsSupported(boolean geoSmsSupported) { + this.mGeoSmsSupported = geoSmsSupported; + } + + /** + * Checks whether RCS call composer is supported. + */ + public boolean isCallComposerSupported() { + return mCallComposerSupported; + } + + /** + * Sets call composer as supported or not supported. + */ + public void setCallComposerSupported(boolean callComposerSupported) { + this.mCallComposerSupported = callComposerSupported; + } + + /** + * Checks whether post call is supported. + */ + public boolean isPostCallSupported(){ + return mPostCallSupported; + } + + /** + * Sets post call as supported or not supported. + */ + public void setPostCallSupported(boolean postCallSupported) { + this.mPostCallSupported = postCallSupported; + } + + /** + * Checks whether shared map is supported. + */ + public boolean isSharedMapSupported() { + return mSharedMapSupported; + } + + /** + * Sets shared map as supported or not supported. + */ + public void setSharedMapSupported(boolean sharedMapSupported) { + this.mSharedMapSupported = sharedMapSupported; + } + + /** + * Checks whether shared sketch is supported. + */ + public boolean isSharedSketchSupported() { + return mSharedSketchSupported; + } + + /** + * Sets shared sketch as supported or not supported. + */ + public void setSharedSketchSupported(boolean sharedSketchSupported) { + this.mSharedSketchSupported = sharedSketchSupported; + } + + /** + * Checks whether chatbot communication is supported. + */ + public boolean isChatbotSupported() { + return mChatbotSupported; + } + + /** + * Sets chatbot communication as supported or not supported. + */ + public void setChatbotSupported(boolean chatbotSupported) { + this.mChatbotSupported = chatbotSupported; + } + + /** + * Checks whether chatbot role is supported. + */ + public boolean isChatbotRoleSupported() { + return mChatbotRoleSupported; + } + + /** + * Sets chatbot role as supported or not supported. + */ + public void setChatbotRoleSupported(boolean chatbotRoleSupported) { + this.mChatbotRoleSupported = chatbotRoleSupported; + } + /** Gets the list of supported extensions. */ public String[] getExts() { return mExts; @@ -434,6 +546,13 @@ public class CapInfo implements Parcelable { dest.writeInt(mGeoPushSupported ? 1 : 0); dest.writeInt(mSmSupported ? 1 : 0); dest.writeInt(mFullSnFGroupChatSupported ? 1 : 0); + dest.writeInt(mGeoSmsSupported ? 1 : 0); + dest.writeInt(mCallComposerSupported ? 1 : 0); + dest.writeInt(mPostCallSupported ? 1 : 0); + dest.writeInt(mSharedMapSupported ? 1 : 0); + dest.writeInt(mSharedSketchSupported ? 1 : 0); + dest.writeInt(mChatbotSupported ? 1 : 0); + dest.writeInt(mChatbotRoleSupported ? 1 : 0); dest.writeInt(mRcsIpVoiceCallSupported ? 1 : 0); dest.writeInt(mRcsIpVideoCallSupported ? 1 : 0); @@ -476,6 +595,13 @@ public class CapInfo implements Parcelable { mGeoPushSupported = (source.readInt() == 0) ? false : true; mSmSupported = (source.readInt() == 0) ? false : true; mFullSnFGroupChatSupported = (source.readInt() == 0) ? false : true; + mGeoSmsSupported = (source.readInt() == 0) ? false : true; + mCallComposerSupported = (source.readInt() == 0) ? false : true; + mPostCallSupported = (source.readInt() == 0) ? false : true; + mSharedMapSupported = (source.readInt() == 0) ? false : true; + mSharedSketchSupported = (source.readInt() == 0) ? false : true; + mChatbotSupported = (source.readInt() == 0) ? false : true; + mChatbotRoleSupported = (source.readInt() == 0) ? false : true; mRcsIpVoiceCallSupported = (source.readInt() == 0) ? false : true; mRcsIpVideoCallSupported = (source.readInt() == 0) ? false : true; diff --git a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java index a50a22f68fa0..fdff86f9669f 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java +++ b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java @@ -47,6 +47,10 @@ public class PresPublishTriggerType implements Parcelable { public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN = 8; /** Trigger is unknown. */ public static final int UCE_PRES_PUBLISH_TRIGGER_UNKNOWN = 9; + /** Move to 5G NR with VoPS disabled. */ + public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10; + /** Move to 5G NR with VoPS enabled. */ + public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11; @@ -113,4 +117,4 @@ public class PresPublishTriggerType implements Parcelable { public void readFromParcel(Parcel source) { mPublishTriggerType = source.readInt(); } -}
\ No newline at end of file +} diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl index d94294f0aa22..74bfb963c6b0 100644 --- a/core/java/com/android/internal/app/ISoundTriggerService.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl @@ -61,10 +61,6 @@ interface ISoundTriggerService { int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam, int value); - /** - * @throws UnsupportedOperationException if hal or model do not support this API. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - */ int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam); @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId, diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index f462f5d2571d..be2d1d60e9a2 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractionSessionListener; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -91,6 +92,49 @@ interface IVoiceInteractionManagerService { */ int stopRecognition(in IVoiceInteractionService service, int keyphraseId, in IRecognitionStatusCallback callback); + /** + * Set a model specific ModelParams with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * queryParameter should be checked first before calling this method. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + int setParameter(in IVoiceInteractionService service, int keyphraseId, + in ModelParams modelParam, int value); + /** + * Get a model specific ModelParams. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See ModelParams for parameter default values. + * queryParameter should be checked first before calling this method. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return value of parameter + */ + int getParameter(in IVoiceInteractionService service, int keyphraseId, + in ModelParams modelParam); + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling setParameter or getParameter. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return supported range of parameter, null if not supported + */ + @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service, + int keyphraseId, in ModelParams modelParam); /** * @return the component name for the currently active voice interaction service diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 9e23c28a0711..9fbc1b74c9ae 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -350,7 +350,12 @@ public final class SystemUiDeviceConfigFlags { * (boolean) Whether screenshot flow going to the corner (instead of shown in a notification) * is enabled. */ - public static final String SCREENSHOT_CORNER_FLOW = "screenshot_corner_flow"; + public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow"; + + /** + * (boolean) Whether scrolling screenshots are enabled. + */ + public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling"; private SystemUiDeviceConfigFlags() { } diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index cca97f689f4e..3a450de6c8e6 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -16,6 +16,9 @@ package com.android.internal.logging; +import android.annotation.NonNull; +import android.annotation.Nullable; + /** * Logging interface for UI events. Normal implementation is UiEventLoggerImpl. * For testing, use fake implementation UiEventLoggerFake. @@ -26,13 +29,24 @@ public interface UiEventLogger { /** Put your Event IDs in enums that implement this interface, and document them using the * UiEventEnum annotation. * Event IDs must be globally unique. This will be enforced by tooling (forthcoming). - * OEMs should use event IDs above 100000. + * OEMs should use event IDs above 100000 and below 1000000 (1 million). */ interface UiEventEnum { int getId(); } + + /** + * Log a simple event, with no package information. Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + */ + void log(@NonNull UiEventEnum event); + /** - * Log a simple event, with no package or instance ID. + * Log an event with package information. Does nothing if event.getId() <= 0. + * Give both uid and packageName if both are known, but one may be omitted if unknown. + * @param event an enum implementing UiEventEnum interface. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). */ - void log(UiEventEnum eventID); + void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index e64fba2d2a31..bdf460c710c3 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -24,14 +24,16 @@ import android.util.StatsLog; * See UiEventReported atom in atoms.proto for more context. */ public class UiEventLoggerImpl implements UiEventLogger { - /** - * Log a simple event, with no package or instance ID. - */ @Override public void log(UiEventEnum event) { + log(event, 0, null); + } + + @Override + public void log(UiEventEnum event, int uid, String packageName) { final int eventID = event.getId(); if (eventID > 0) { - StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null); + StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName); } } } diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 92e9bbb77bd3..6be5b81afee2 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -30,7 +30,7 @@ public class UiEventLoggerFake implements UiEventLogger { /** * Immutable data class used to record fake log events. */ - public class FakeUiEvent { + public static class FakeUiEvent { public final int eventId; public final int uid; public final String packageName; @@ -44,15 +44,20 @@ public class UiEventLoggerFake implements UiEventLogger { private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>(); + public Queue<FakeUiEvent> getLogs() { + return mLogs; + } + @Override public void log(UiEventEnum event) { + log(event, 0, null); + } + + @Override + public void log(UiEventEnum event, int uid, String packageName) { final int eventId = event.getId(); if (eventId > 0) { - mLogs.offer(new FakeUiEvent(eventId, 0, null)); + mLogs.offer(new FakeUiEvent(eventId, uid, packageName)); } } - - public Queue<FakeUiEvent> getLogs() { - return mLogs; - } } diff --git a/core/java/com/android/internal/util/ConnectivityUtil.java b/core/java/com/android/internal/util/ConnectivityUtil.java new file mode 100644 index 000000000000..799352b9ec15 --- /dev/null +++ b/core/java/com/android/internal/util/ConnectivityUtil.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.Manifest; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + + +/** + * Utility methods for common functionality using by different networks. + * + * @hide + */ +public class ConnectivityUtil { + + private static final String TAG = "ConnectivityUtil"; + + private final Context mContext; + private final AppOpsManager mAppOps; + private final UserManager mUserManager; + + public ConnectivityUtil(Context context) { + mContext = context; + mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + + /** + * API to determine if the caller has fine/coarse location permission (depending on + * config/targetSDK level) and the location mode is enabled for the user. SecurityException is + * thrown if the caller has no permission or the location mode is disabled. + * @param pkgName package name of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + */ + public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid, + @Nullable String message) + throws SecurityException { + checkPackage(uid, pkgName); + + // Location mode must be enabled + if (!isLocationModeEnabled()) { + // Location mode is disabled, scan results cannot be returned + throw new SecurityException("Location mode is disabled for the device"); + } + + // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to + // location information. + boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId, + uid, /* coarseForTargetSdkLessThanQ */ true, message); + + // If neither caller or app has location access, there is no need to check + // any other permissions. Deny access to scan results. + if (!canAppPackageUseLocation) { + throw new SecurityException("UID " + uid + " has no location permission"); + } + // If the User or profile is current, permission is granted + // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. + if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) { + throw new SecurityException("UID " + uid + " profile not permitted"); + } + } + + /** + * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or + * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level) + * and a corresponding app op is allowed for this package and uid. + * + * @param pkgName PackageName of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE + * else (false or targetSDK >= Q) then will check for FINE + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + */ + public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId, + int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) { + boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid); + + String permissionType = Manifest.permission.ACCESS_FINE_LOCATION; + if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { + // Having FINE permission implies having COARSE permission (but not the reverse) + permissionType = Manifest.permission.ACCESS_COARSE_LOCATION; + } + if (getUidPermission(permissionType, uid) + == PackageManager.PERMISSION_DENIED) { + return false; + } + + // Always checking FINE - even if will not enforce. This will record the request for FINE + // so that a location request by the app is surfaced to the user. + boolean isFineLocationAllowed = noteAppOpAllowed( + AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message); + if (isFineLocationAllowed) { + return true; + } + if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { + return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid, + message); + } + return false; + } + + /** + * Retrieves a handle to LocationManager (if not already done) and check if location is enabled. + */ + public boolean isLocationModeEnabled() { + LocationManager locationManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + try { + return locationManager.isLocationEnabledForUser(UserHandle.of( + getCurrentUser())); + } catch (Exception e) { + Log.e(TAG, "Failure to get location mode via API, falling back to settings", e); + return false; + } + } + + private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) { + long ident = Binder.clearCallingIdentity(); + try { + if (mContext.getPackageManager().getApplicationInfoAsUser( + packageName, 0, + UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion + < versionCode) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume unknown app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking App's version. + } finally { + Binder.restoreCallingIdentity(ident); + } + return false; + } + + private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId, + int uid, @Nullable String message) { + return mAppOps.noteOp(op, uid, pkgName, featureId, message) == AppOpsManager.MODE_ALLOWED; + } + + private void checkPackage(int uid, String pkgName) throws SecurityException { + if (pkgName == null) { + throw new SecurityException("Checking UID " + uid + " but Package Name is Null"); + } + mAppOps.checkPackage(uid, pkgName); + } + + private boolean isCurrentProfile(int uid) { + UserHandle currentUser = UserHandle.of(getCurrentUser()); + UserHandle callingUser = UserHandle.getUserHandleForUid(uid); + return currentUser.equals(callingUser) + || mUserManager.isSameProfileGroup(currentUser, callingUser); + } + + private boolean checkInteractAcrossUsersFull(int uid) { + return getUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid) + == PackageManager.PERMISSION_GRANTED; + } + + @VisibleForTesting + protected int getCurrentUser() { + return ActivityManager.getCurrentUser(); + } + + private int getUidPermission(String permissionType, int uid) { + // We don't care about pid, pass in -1 + return mContext.checkPermission(permissionType, -1, uid); + } +} diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 5bc96d8ee1d3..408a7a8e139a 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -126,7 +126,9 @@ public class Preconditions { * @param reference an object reference * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null + * @deprecated - use {@link java.util.Objects.requireNonNull} instead. */ + @Deprecated @UnsupportedAppUsage public static @NonNull <T> T checkNotNull(final T reference) { if (reference == null) { @@ -144,7 +146,9 @@ public class Preconditions { * be converted to a string using {@link String#valueOf(Object)} * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null + * @deprecated - use {@link java.util.Objects.requireNonNull} instead. */ + @Deprecated @UnsupportedAppUsage public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) { if (reference == null) { @@ -154,26 +158,6 @@ public class Preconditions { } /** - * Ensures that an object reference passed as a parameter to the calling - * method is not null. - * - * @param reference an object reference - * @param messageTemplate a printf-style message template to use if the check fails; will - * be converted to a string using {@link String#format(String, Object...)} - * @param messageArgs arguments for {@code messageTemplate} - * @return the non-null reference that was validated - * @throws NullPointerException if {@code reference} is null - */ - public static @NonNull <T> T checkNotNull(final T reference, - final String messageTemplate, - final Object... messageArgs) { - if (reference == null) { - throw new NullPointerException(String.format(messageTemplate, messageArgs)); - } - return reference; - } - - /** * Ensures the truth of an expression involving the state of the calling * instance, but not involving any parameters to the calling method. * diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java index 43a227a32346..d7a01c4762f1 100644 --- a/core/java/com/android/internal/widget/RecyclerView.java +++ b/core/java/com/android/internal/widget/RecyclerView.java @@ -2011,13 +2011,27 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } if (!dispatchNestedPreFling(velocityX, velocityY)) { - final boolean canScroll = canScrollHorizontal || canScrollVertical; - dispatchNestedFling(velocityX, velocityY, canScroll); + final View firstChild = mLayout.getChildAt(0); + final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1); + boolean consumed = false; + if (velocityY < 0) { + consumed = getChildAdapterPosition(firstChild) > 0 + || firstChild.getTop() < getPaddingTop(); + } + + if (velocityY > 0) { + consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1 + || lastChild.getBottom() > getHeight() - getPaddingBottom(); + } + + dispatchNestedFling(velocityX, velocityY, consumed); if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { return true; } + final boolean canScroll = canScrollHorizontal || canScrollVertical; + if (canScroll) { velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index fa64fd10d6db..adedffdd731c 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -52,42 +52,34 @@ jmethodID gBitmapConfig_nativeToConfigMethodID; using namespace android; -jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) { - const char* mimeType; +const char* getMimeType(SkEncodedImageFormat format) { switch (format) { case SkEncodedImageFormat::kBMP: - mimeType = "image/bmp"; - break; + return "image/bmp"; case SkEncodedImageFormat::kGIF: - mimeType = "image/gif"; - break; + return "image/gif"; case SkEncodedImageFormat::kICO: - mimeType = "image/x-ico"; - break; + return "image/x-ico"; case SkEncodedImageFormat::kJPEG: - mimeType = "image/jpeg"; - break; + return "image/jpeg"; case SkEncodedImageFormat::kPNG: - mimeType = "image/png"; - break; + return "image/png"; case SkEncodedImageFormat::kWEBP: - mimeType = "image/webp"; - break; + return "image/webp"; case SkEncodedImageFormat::kHEIF: - mimeType = "image/heif"; - break; + return "image/heif"; case SkEncodedImageFormat::kWBMP: - mimeType = "image/vnd.wap.wbmp"; - break; + return "image/vnd.wap.wbmp"; case SkEncodedImageFormat::kDNG: - mimeType = "image/x-adobe-dng"; - break; + return "image/x-adobe-dng"; default: - mimeType = nullptr; - break; + return nullptr; } +} +jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) { jstring jstr = nullptr; + const char* mimeType = getMimeType(format); if (mimeType) { // NOTE: Caller should env->ExceptionCheck() for OOM // (can't check for nullptr as it's a valid return value) @@ -289,10 +281,9 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, // Set the options and return if the client only wants the size. if (options != NULL) { - jstring mimeType = encodedFormatToString( - env, (SkEncodedImageFormat)codec->getEncodedFormat()); + jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat()); if (env->ExceptionCheck()) { - return nullObjectReturn("OOM in encodedFormatToString()"); + return nullObjectReturn("OOM in getMimeTypeAsJavaString()"); } env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h index e37c98dc66ff..45bffc44967d 100644 --- a/core/jni/android/graphics/BitmapFactory.h +++ b/core/jni/android/graphics/BitmapFactory.h @@ -26,6 +26,6 @@ extern jfieldID gOptions_bitmapFieldID; extern jclass gBitmapConfig_class; extern jmethodID gBitmapConfig_nativeToConfigMethodID; -jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format); +jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat); #endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_ diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index f18632dfc403..06b4ff849097 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -197,7 +197,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); env->SetObjectField(options, gOptions_mimeFieldID, - encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat())); + getMimeTypeAsJavaString(env, brd->getEncodedFormat())); if (env->ExceptionCheck()) { return nullObjectReturn("OOM in encodedFormatToString()"); } diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index 627f8f5b3e49..a9002867ae10 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -475,7 +475,7 @@ static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong native static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); - return encodedFormatToString(env, decoder->mCodec->getEncodedFormat()); + return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat()); } static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/core/jni/android/graphics/MimeType.h index 306de84fe130..38a579c595e4 100644 --- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java +++ b/core/jni/android/graphics/MimeType.h @@ -14,18 +14,8 @@ * limitations under the License. */ -package android.media.tv.tuner.filter; +#pragma once -/** - * Extra Meta Data from AD (Audio Descriptor) according to - * ETSI TS 101 154 V2.1.1. - * @hide - */ -public class AudioExtraMetaData { - private byte mAdFade; - private byte mAdPan; - private byte mVersionTextTag; - private byte mAdGainCenter; - private byte mAdGainFront; - private byte mAdGainSurround; -} +#include "SkEncodedImageFormat.h" + +const char* getMimeType(SkEncodedImageFormat); diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp index 0f56779135b8..90cc98699827 100644 --- a/core/jni/android/graphics/apex/android_bitmap.cpp +++ b/core/jni/android/graphics/apex/android_bitmap.cpp @@ -122,99 +122,6 @@ AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmapHandle) { return getInfo(bitmap->info(), bitmap->rowBytes()); } -static bool nearlyEqual(float a, float b) { - // By trial and error, this is close enough to match for the ADataSpaces we - // compare for. - return ::fabs(a-b) < .002f; -} - -static bool nearlyEqual(const skcms_TransferFunction& x, const skcms_TransferFunction& y) { - return nearlyEqual(x.g, y.g) - && nearlyEqual(x.a, y.a) - && nearlyEqual(x.b, y.b) - && nearlyEqual(x.c, y.c) - && nearlyEqual(x.d, y.d) - && nearlyEqual(x.e, y.e) - && nearlyEqual(x.f, y.f); -} - -static bool nearlyEqual(const skcms_Matrix3x3& x, const skcms_Matrix3x3& y) { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - if (!nearlyEqual(x.vals[i][j], y.vals[i][j])) return false; - } - } - return true; -} - -static constexpr skcms_TransferFunction k2Dot6 = - { 2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - -// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut -// matches the white point used by ColorSpace.Named.DCIP3. -static constexpr skcms_Matrix3x3 kDCIP3 = {{ - { 0.486143, 0.323835, 0.154234 }, - { 0.226676, 0.710327, 0.0629966 }, - { 0.000800549, 0.0432385, 0.78275 }, -}}; - -ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) { - Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); - const SkImageInfo& info = bitmap->info(); - SkColorSpace* colorSpace = info.colorSpace(); - if (!colorSpace) { - return ADATASPACE_UNKNOWN; - } - - if (colorSpace->isSRGB()) { - if (info.colorType() == kRGBA_F16_SkColorType) { - return ADATASPACE_SCRGB; - } - return ADATASPACE_SRGB; - } - - skcms_TransferFunction fn; - LOG_ALWAYS_FATAL_IF(!colorSpace->isNumericalTransferFn(&fn)); - - skcms_Matrix3x3 gamut; - LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&gamut)); - - if (nearlyEqual(gamut, SkNamedGamut::kSRGB)) { - if (nearlyEqual(fn, SkNamedTransferFn::kLinear)) { - // Skia doesn't differentiate amongst the RANGES. In Java, we associate - // LINEAR_EXTENDED_SRGB with F16, and LINEAR_SRGB with other Configs. - // Make the same association here. - if (info.colorType() == kRGBA_F16_SkColorType) { - return ADATASPACE_SCRGB_LINEAR; - } - return ADATASPACE_SRGB_LINEAR; - } - - if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) { - return ADATASPACE_BT709; - } - } - - if (nearlyEqual(fn, SkNamedTransferFn::kSRGB) && nearlyEqual(gamut, SkNamedGamut::kDCIP3)) { - return ADATASPACE_DISPLAY_P3; - } - - if (nearlyEqual(fn, SkNamedTransferFn::k2Dot2) && nearlyEqual(gamut, SkNamedGamut::kAdobeRGB)) { - return ADATASPACE_ADOBE_RGB; - } - - if (nearlyEqual(fn, SkNamedTransferFn::kRec2020) - && nearlyEqual(gamut, SkNamedGamut::kRec2020)) { - return ADATASPACE_BT2020; - } - - if (nearlyEqual(fn, k2Dot6) && nearlyEqual(gamut, kDCIP3)) { - return ADATASPACE_DCI_P3; - } - - return ADATASPACE_UNKNOWN; -} - AndroidBitmapInfo ABitmap_getInfoFromJava(JNIEnv* env, jobject bitmapObj) { uint32_t rowBytes = 0; SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes); diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h index 32b8a450e147..f231eeddb7e2 100644 --- a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h +++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h @@ -17,7 +17,6 @@ #define ANDROID_GRAPHICS_BITMAP_H #include <android/bitmap.h> -#include <android/data_space.h> #include <jni.h> #include <sys/cdefs.h> @@ -50,7 +49,6 @@ void ABitmap_acquireRef(ABitmap* bitmap); void ABitmap_releaseRef(ABitmap* bitmap); AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmap); -ADataSpace ABitmap_getDataSpace(ABitmap* bitmap); void* ABitmap_getPixels(ABitmap* bitmap); void ABitmap_notifyPixelsChanged(ABitmap* bitmap); @@ -108,7 +106,6 @@ namespace graphics { ABitmap* get() const { return mBitmap; } AndroidBitmapInfo getInfo() const { return ABitmap_getInfo(mBitmap); } - ADataSpace getDataSpace() const { return ABitmap_getDataSpace(mBitmap); } void* getPixels() const { return ABitmap_getPixels(mBitmap); } void notifyPixelsChanged() const { ABitmap_notifyPixelsChanged(mBitmap); } @@ -122,4 +119,4 @@ namespace graphics { }; // namespace android #endif // __cplusplus -#endif // ANDROID_GRAPHICS_BITMAP_H +#endif // ANDROID_GRAPHICS_BITMAP_H
\ No newline at end of file diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 79cf0191057d..53327bc9d99c 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2312,6 +2312,48 @@ android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thi return jStatus; } +static jint +android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz, + jobject jaa, jobjectArray jDeviceArray) +{ + const jsize maxResultSize = env->GetArrayLength(jDeviceArray); + // the JNI is always expected to provide us with an array capable of holding enough + // devices i.e. the most we ever route a track to. This is preferred over receiving an ArrayList + // with reverse JNI to make the array grow as need as this would be less efficient, and some + // components call this method often + if (jDeviceArray == nullptr || maxResultSize == 0) { + ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); + if (jStatus != (jint) AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + AudioDeviceTypeAddrVector devices; + jStatus = check_AudioSystem_Command( + AudioSystem::getDevicesForAttributes(*(paa.get()), &devices)); + if (jStatus != NO_ERROR) { + return jStatus; + } + + if (devices.size() > maxResultSize) { + return AUDIO_JAVA_INVALID_OPERATION; + } + size_t index = 0; + jobject jAudioDeviceAddress = NULL; + for (const auto& device : devices) { + jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress); + } + return jStatus; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -2395,6 +2437,7 @@ static const JNINativeMethod gMethods[] = { {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy}, {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy}, {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy}, + {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes} }; static const JNINativeMethod gEventHandlerMethods[] = { diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index ff596b440867..062b886f54a1 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -220,8 +220,12 @@ static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& valu // ---------------------------------------------------------------------------- +static std::unique_ptr<DynamicLibManager> sDynamicLibManager = + std::make_unique<DynamicLibManager>(); + // Let the opaque type AAssetManager refer to a guarded AssetManager2 instance. struct GuardedAssetManager : public ::AAssetManager { + GuardedAssetManager() : guarded_assetmanager(sDynamicLibManager.get()) {} Guarded<AssetManager2> guarded_assetmanager; }; diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 0992bebb5be0..fb8e633fec12 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -537,9 +537,9 @@ public: LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this); if (mObject != NULL) { JNIEnv* env = javavm_to_jnienv(mVM); - + jobject jBinderProxy = javaObjectForIBinder(env, who.promote()); env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, - gBinderProxyOffsets.mSendDeathNotice, mObject); + gBinderProxyOffsets.mSendDeathNotice, mObject, jBinderProxy); if (env->ExceptionCheck()) { jthrowable excep = env->ExceptionOccurred(); report_exception(env, excep, @@ -1532,8 +1532,9 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz); gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance", "(JJ)Landroid/os/BinderProxy;"); - gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", - "(Landroid/os/IBinder$DeathRecipient;)V"); + gBinderProxyOffsets.mSendDeathNotice = + GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", + "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V"); gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); clazz = FindClassOrDie(env, "java/lang/Class"); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 4a7276c4f94e..b47080f787a1 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -263,7 +263,8 @@ static jobject nativeScreenshot(JNIEnv* env, jclass clazz, status_t res = ScreenshotClient::capture(displayToken, dataspace, ui::PixelFormat::RGBA_8888, sourceCrop, width, height, - useIdentityTransform, rotation, captureSecureLayers, &buffer, capturedSecureLayers); + useIdentityTransform, ui::toRotation(rotation), + captureSecureLayers, &buffer, capturedSecureLayers); if (res != NO_ERROR) { return NULL; } @@ -724,7 +725,8 @@ static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz, { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->setDisplayProjection(token, orientation, layerStackRect, displayRect); + transaction->setDisplayProjection(token, static_cast<ui::Rotation>(orientation), + layerStackRect, displayRect); } } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 30e1e8fb5de6..673772a52212 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -746,9 +746,6 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, const userid_t user_id = multiuser_get_user_id(uid); const std::string user_source = StringPrintf("/mnt/user/%d", user_id); - const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id); - bool isFuse = GetBoolProperty(kPropFuse, false); - // Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to // /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to // AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps) @@ -757,9 +754,15 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL, multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn); + bool isFuse = GetBoolProperty(kPropFuse, false); + if (isFuse) { if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) { + const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id); BindMount(pass_through_source, "/storage", fail_fn); + } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) { + const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id); + BindMount(installer_source, "/storage", fail_fn); } else { BindMount(user_source, "/storage", fail_fn); } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index b83b31c1303a..cd3887e276ff 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2553,4 +2553,8 @@ enum PageId { // OS: R MANAGE_EXTERNAL_STORAGE = 1822; + // Open: Settings > DND > People + // OS: R + DND_PEOPLE = 1823; + } diff --git a/core/proto/android/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto index c6925f448a58..70627edf2cb3 100644 --- a/core/proto/android/server/animationadapter.proto +++ b/core/proto/android/server/animationadapter.proto @@ -50,7 +50,6 @@ message AnimationSpecProto { optional WindowAnimationSpecProto window = 1; optional MoveAnimationSpecProto move = 2; optional AlphaAnimationSpecProto alpha = 3; - optional RotationAnimationSpecProto rotate = 4; } /* represents WindowAnimationSpec */ @@ -77,12 +76,3 @@ message AlphaAnimationSpecProto { optional float to = 2; optional int64 duration_ms = 3; } - -/* represents RotationAnimationSpec */ -message RotationAnimationSpecProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - - optional float start_luma = 1; - optional float end_luma = 2; - optional int64 duration_ms = 3; -} diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index 557075cc5bfa..2de5b7fd45f6 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -32,6 +32,10 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } option (android.msg_privacy).dest = DEST_AUTOMATIC; // The package name of the app @@ -54,6 +58,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 9054d5462da5..0fca1d19c0e5 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -154,4 +154,5 @@ enum EventId { SET_AUTO_TIME = 127; SET_AUTO_TIME_ZONE = 128; SET_PACKAGES_PROTECTED = 129; + SET_FACTORY_RESET_PROTECTION = 130; } diff --git a/core/proto/android/stats/launcher/Android.bp b/core/proto/android/stats/launcher/Android.bp index b8fb6ffc6677..976a0b8634a3 100644 --- a/core/proto/android/stats/launcher/Android.bp +++ b/core/proto/android/stats/launcher/Android.bp @@ -25,3 +25,16 @@ java_library { "*.proto", ], } + +java_library { + name: "launcherprotoslite", + proto: { + type: "lite", + include_dirs: ["external/protobuf/src"], + }, + + sdk_version: "current", + srcs: [ + "*.proto", + ], +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 03d34b595e4a..7ecd9b702e53 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -370,6 +370,7 @@ <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" /> <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" /> <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" /> + <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" /> @@ -444,6 +445,7 @@ <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" /> <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" /> + <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" /> <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" /> <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" /> @@ -639,10 +641,6 @@ <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" /> - <!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be - removed. --> - <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" /> - <!-- For tether entitlement recheck--> <protected-broadcast android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" /> @@ -1790,6 +1788,11 @@ android:label="@string/permlab_preferredPaymentInfo" android:protectionLevel="normal" /> + <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs. + @hide --> + <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED" + android:protectionLevel="signature|privileged" /> + <!-- @deprecated This permission used to allow too broad access to sensitive methods and all its uses have been replaced by a more appropriate permission. Most uses have been replaced with a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the @@ -2070,8 +2073,10 @@ android:protectionLevel="signature|privileged" /> <!-- Allows read only access to precise phone state. - @hide Pending API council approval --> + Allows reading of detailed information about phone state for special-use applications + such as dialers, carrier applications, or ims applications. --> <permission android:name="android.permission.READ_PRECISE_PHONE_STATE" + android:permissionGroup="android.permission-group.UNDEFINED" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows read access to privileged phone state. @@ -2563,7 +2568,7 @@ <!-- Allows telephony to suggest the time / time zone. <p>Not for use by third-party applications. - @hide + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide --> <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE" android:protectionLevel="signature|telephony" /> @@ -3381,6 +3386,14 @@ <permission android:name="android.permission.NOTIFY_TV_INPUTS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to interact with tuner resources through + Tuner Resource Manager. + <p>Protection level: signature|privileged + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.TUNER_RESOURCE_ACCESS" + android:protectionLevel="signature|privileged" /> + <!-- Must be required by a {@link android.media.routing.MediaRouteService} to ensure that only the system can interact with it. @hide --> diff --git a/core/res/res/anim/screen_rotate_0_enter.xml b/core/res/res/anim/screen_rotate_0_enter.xml index 629be7ea2d30..93cf3652d185 100644 --- a/core/res/res/anim/screen_rotate_0_enter.xml +++ b/core/res/res/anim/screen_rotate_0_enter.xml @@ -1,25 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2019 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> +/* +** Copyright 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. +*/ +--> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:interpolator="@interpolator/screen_rotation_alpha_in" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_in" /> + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml index fa046a036855..37d5a4115621 100644 --- a/core/res/res/anim/screen_rotate_0_exit.xml +++ b/core/res/res/anim/screen_rotate_0_exit.xml @@ -1,25 +1,22 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2019 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> +/* +** Copyright 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. +*/ +--> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/screen_rotation_alpha_out" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:shareInterpolator="false"> </set> diff --git a/core/res/res/anim/screen_rotate_0_frame.xml b/core/res/res/anim/screen_rotate_0_frame.xml new file mode 100644 index 000000000000..5ea9bf8205e3 --- /dev/null +++ b/core/res/res/anim/screen_rotate_0_frame.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime" /> +</set> diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml index 889a615e07f4..688a8d5bb2aa 100644 --- a/core/res/res/anim/screen_rotate_180_enter.xml +++ b/core/res/res/anim/screen_rotate_180_enter.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="180" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_180" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml index 766fcfae1f91..58a1868bd398 100644 --- a/core/res/res/anim/screen_rotate_180_exit.xml +++ b/core/res/res/anim/screen_rotate_180_exit.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="-180" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_180" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set>
\ No newline at end of file diff --git a/core/res/res/anim/screen_rotate_alpha.xml b/core/res/res/anim/screen_rotate_alpha.xml index 2cac982e24b4..c49ef9cafd39 100644 --- a/core/res/res/anim/screen_rotate_alpha.xml +++ b/core/res/res/anim/screen_rotate_alpha.xml @@ -20,8 +20,8 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/screen_rotation_alpha_out" + android:interpolator="@interpolator/decelerate_quint" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml index 87fd25ea4603..b16d5fc761ee 100644 --- a/core/res/res/anim/screen_rotate_minus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml @@ -18,17 +18,19 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase anim <rotate android:fromDegrees="-90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/screen_rotation_alpha_in" - android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" - android:duration="@android:integer/config_screen_rotation_fade_in" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <rotate android:fromDegrees="-90" android:toDegrees="0" + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml index c3aee14dc235..0927dd30ceb3 100644 --- a/core/res/res/anim/screen_rotate_minus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml @@ -18,16 +18,26 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase animation <rotate android:fromDegrees="0" android:toDegrees="90" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/screen_rotation_alpha_out" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <scale android:fromXScale="100%" android:toXScale="100%p" + android:fromYScale="100%" android:toYScale="100%p" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> + <rotate android:fromDegrees="0" android:toDegrees="90" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml index 8849db421e75..86a8d24cbbcc 100644 --- a/core/res/res/anim/screen_rotate_plus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml @@ -18,16 +18,19 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase animation <rotate android:fromDegrees="90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:fillEnabled="true" - android:interpolator="@interpolator/screen_rotation_alpha_in" - android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" - android:duration="@android:integer/config_screen_rotation_fade_in" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <rotate android:fromDegrees="90" android:toDegrees="0" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml index de84c3bd08fc..fd786f9afce0 100644 --- a/core/res/res/anim/screen_rotate_plus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml @@ -18,16 +18,26 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase animation <rotate android:fromDegrees="0" android:toDegrees="-90" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/screen_rotation_alpha_out" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <scale android:fromXScale="100%" android:toXScale="100%p" + android:fromYScale="100%" android:toYScale="100%p" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> + <rotate android:fromDegrees="0" android:toDegrees="-90" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 245aed1484f7..6f554f0264df 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -147,24 +147,6 @@ <integer name="config_activityShortDur">150</integer> <integer name="config_activityDefaultDur">220</integer> - <!-- Fade out time for screen rotation --> - <integer name="config_screen_rotation_fade_out">116</integer> - - <!-- Fade in time for screen rotation --> - <integer name="config_screen_rotation_fade_in">233</integer> - - <!-- Fade in delay time for screen rotation --> - <integer name="config_screen_rotation_fade_in_delay">100</integer> - - <!-- Total time for 90 degree screen rotation animations --> - <integer name="config_screen_rotation_total_90">333</integer> - - <!-- Total time for 180 degree screen rotation animation --> - <integer name="config_screen_rotation_total_180">433</integer> - - <!-- Total time for the rotation background color transition --> - <integer name="config_screen_rotation_color_transition">200</integer> - <!-- The duration (in milliseconds) of the tooltip show/hide animations. --> <integer name="config_tooltipAnimTime">150</integer> @@ -1895,6 +1877,8 @@ <string name="config_defaultCallRedirection" translatable="false"></string> <!-- The name of the package that will hold the call screening role by default. --> <string name="config_defaultCallScreening" translatable="false"></string> + <!-- The name of the package that will hold the system gallery role. --> + <string name="config_systemGallery" translatable="false">com.android.gallery</string> <!-- Enable/disable default bluetooth profiles: HSP_AG, ObexObjectPush, Audio, NAP --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 817ccde98504..6cf6a6828237 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3000,7 +3000,6 @@ <public-group type="attr" first-id="0x01010607"> <public name="importantForContentCapture" /> <public name="forceQueryable" /> - <!-- @hide @SystemApi --> <public name="resourcesMap" /> <public name="animatedImageDrawable"/> <public name="htmlDescription"/> @@ -3031,6 +3030,8 @@ <public name="config_defaultCallRedirection" /> <!-- @hide @SystemApi --> <public name="config_defaultCallScreening" /> + <!-- @hide @SystemApi @TestApi --> + <public name="config_systemGallery" /> </public-group> <public-group type="bool" first-id="0x01110005"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 973d5f6392f4..9e117498eb10 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1805,6 +1805,7 @@ <!-- From services --> <java-symbol type="anim" name="screen_rotate_0_enter" /> <java-symbol type="anim" name="screen_rotate_0_exit" /> + <java-symbol type="anim" name="screen_rotate_0_frame" /> <java-symbol type="anim" name="screen_rotate_180_enter" /> <java-symbol type="anim" name="screen_rotate_180_exit" /> <java-symbol type="anim" name="screen_rotate_180_frame" /> @@ -1980,7 +1981,6 @@ <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" /> <java-symbol type="integer" name="config_brightness_ramp_rate_fast" /> <java-symbol type="integer" name="config_brightness_ramp_rate_slow" /> - <java-symbol type="integer" name="config_screen_rotation_color_transition" /> <java-symbol type="layout" name="am_compat_mode_dialog" /> <java-symbol type="layout" name="launch_warning" /> <java-symbol type="layout" name="safe_mode" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index caae9088e9b5..2df6d1ca0e2d 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -8,6 +8,7 @@ android_test { "EnabledTestApp/src/**/*.java", "BinderProxyCountingTestApp/src/**/*.java", "BinderProxyCountingTestService/src/**/*.java", + "BinderDeathRecipientHelperApp/src/**/*.java", "aidl/**/I*.aidl", ], @@ -59,7 +60,11 @@ android_test { resource_dirs: ["res"], resource_zips: [":FrameworksCoreTests_apks_as_resources"], - data: [":BstatsTestApp"], + data: [ + ":BstatsTestApp", + ":BinderDeathRecipientHelperApp1", + ":BinderDeathRecipientHelperApp2", + ], } // Rules to copy all the test apks to the intermediate raw resource directory diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 1aea98a93afe..b85a332e9000 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -95,6 +95,7 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.KILL_UID" /> + <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index b40aa87cb78b..ed9d3f54ef3d 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -21,6 +21,8 @@ <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksCoreTests.apk" /> <option name="test-file-name" value="BstatsTestApp.apk" /> + <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" /> + <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" /> </target_preparer> <option name="test-tag" value="FrameworksCoreTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp new file mode 100644 index 000000000000..25e4fc366124 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp @@ -0,0 +1,19 @@ +android_test_helper_app { + name: "BinderDeathRecipientHelperApp1", + + srcs: ["**/*.java"], + + sdk_version: "current", +} + +android_test_helper_app { + name: "BinderDeathRecipientHelperApp2", + + srcs: ["**/*.java"], + + sdk_version: "current", + + aaptflags: [ + "--rename-manifest-package com.android.frameworks.coretests.bdr_helper_app2", + ], +} diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml new file mode 100644 index 000000000000..dbd1774b7d79 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.bdr_helper_app1"> + + <application> + <receiver android:name="com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver" + android:exported="true"/> + + </application> +</manifest> diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java new file mode 100644 index 000000000000..ab79e69d1351 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java @@ -0,0 +1,51 @@ +/* + * 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.frameworks.coretests.bdr_helper_app; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.util.Log; + +/** + * Receiver used to hand off a binder owned by this process to + * {@link android.os.BinderDeathRecipientTest}. + */ +public class TestCommsReceiver extends BroadcastReceiver { + private static final String TAG = TestCommsReceiver.class.getSimpleName(); + private static final String PACKAGE_NAME = "com.android.frameworks.coretests.bdr_helper_app"; + + public static final String ACTION_GET_BINDER = PACKAGE_NAME + ".action.GET_BINDER"; + public static final String EXTRA_KEY_BINDER = PACKAGE_NAME + ".EXTRA_BINDER"; + + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case ACTION_GET_BINDER: + final Bundle resultExtras = new Bundle(); + resultExtras.putBinder(EXTRA_KEY_BINDER, new Binder()); + setResult(Activity.RESULT_OK, null, resultExtras); + break; + default: + Log.e(TAG, "Unknown action " + intent.getAction()); + break; + } + } +} diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java index c6c0b46d0505..b13bcd1311f6 100644 --- a/core/tests/coretests/src/android/net/NetworkKeyTest.java +++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java @@ -18,6 +18,7 @@ package android.net; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.net.wifi.ScanResult; @@ -107,7 +108,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_nullSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.BSSID = VALID_BSSID; assertNull(NetworkKey.createFromScanResult(scanResult)); @@ -115,7 +116,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_emptySsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = ""; scanResult.BSSID = VALID_BSSID; @@ -124,7 +125,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_noneSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = WifiManager.UNKNOWN_SSID; scanResult.BSSID = VALID_BSSID; @@ -133,7 +134,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_nullBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; assertNull(NetworkKey.createFromScanResult(scanResult)); @@ -141,7 +142,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_emptyBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = ""; @@ -150,7 +151,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_invalidBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = INVALID_BSSID; @@ -159,7 +160,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_validSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = VALID_BSSID; diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java new file mode 100644 index 000000000000..2cce43f70774 --- /dev/null +++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java @@ -0,0 +1,162 @@ +/* + * 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.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.ArraySet; +import android.util.Log; +import android.util.Pair; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks. + */ +@RunWith(AndroidJUnit4.class) +public class BinderDeathRecipientTest { + private static final String TAG = BinderDeathRecipientTest.class.getSimpleName(); + private static final String TEST_PACKAGE_NAME_1 = + "com.android.frameworks.coretests.bdr_helper_app1"; + private static final String TEST_PACKAGE_NAME_2 = + "com.android.frameworks.coretests.bdr_helper_app2"; + + private Context mContext; + private Handler mHandler; + private ActivityManager mActivityManager; + private Set<Pair<IBinder, IBinder.DeathRecipient>> mLinkedDeathRecipients = new ArraySet<>(); + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mActivityManager = mContext.getSystemService(ActivityManager.class); + mHandler = new Handler(Looper.getMainLooper()); + } + + private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException { + final CountDownLatch resultLatch = new CountDownLatch(1); + final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED); + final AtomicReference<Bundle> resultExtras = new AtomicReference<>(); + + final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER) + .setClassName(testPackage, TestCommsReceiver.class.getName()); + mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + resultCode.set(getResultCode()); + resultExtras.set(getResultExtras(true)); + resultLatch.countDown(); + } + }, mHandler, Activity.RESULT_CANCELED, null, null); + + assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS)); + assertEquals(Activity.RESULT_OK, resultCode.get()); + return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER); + } + + @Test + public void binderDied_noArgs() throws Exception { + final IBinder testAppBinder = getNewRemoteBinder(TEST_PACKAGE_NAME_1); + final CountDownLatch deathNotificationLatch = new CountDownLatch(1); + final IBinder.DeathRecipient simpleDeathRecipient = + () -> deathNotificationLatch.countDown(); + testAppBinder.linkToDeath(simpleDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testAppBinder, simpleDeathRecipient)); + + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1); + assertTrue("Death notification did not arrive", + deathNotificationLatch.await(10, TimeUnit.SECONDS)); + } + + @Test + public void binderDied_iBinderArg() throws Exception { + final IBinder testApp1Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_1); + final IBinder testApp2Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_2); + final CyclicBarrier barrier = new CyclicBarrier(2); + + final AtomicReference<IBinder> binderThatDied = new AtomicReference<>(); + final IBinder.DeathRecipient sameDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Log.e(TAG, "Should not have been called!"); + } + + @Override + public void binderDied(IBinder who) { + binderThatDied.set(who); + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + Log.e(TAG, "Unexpected exception while waiting on CyclicBarrier", e); + } + } + }; + testApp1Binder.linkToDeath(sameDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testApp1Binder, sameDeathRecipient)); + + testApp2Binder.linkToDeath(sameDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testApp2Binder, sameDeathRecipient)); + + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1); + try { + barrier.await(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Timed out while waiting for 1st death notification: " + e.getMessage()); + } + assertEquals("Different binder received", testApp1Binder, binderThatDied.get()); + + barrier.reset(); + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_2); + try { + barrier.await(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Timed out while waiting for 2nd death notification: " + e.getMessage()); + } + assertEquals("Different binder received", testApp2Binder, binderThatDied.get()); + } + + @After + public void tearDown() { + for (Pair<IBinder, IBinder.DeathRecipient> linkedPair : mLinkedDeathRecipients) { + linkedPair.first.unlinkToDeath(linkedPair.second, 0); + } + } +} diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java index d7f50bafdc0a..e3b08bb14e46 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -108,5 +108,30 @@ public class InsetsSourceTest { assertEquals(Insets.of(0, 100, 0, 0), insets); } + @Test + public void testCalculateVisibleInsets_default() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 100, 0, 0), insets); + } + + @Test + public void testCalculateVisibleInsets_override() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisibleFrame(new Rect(0, 0, 500, 200)); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 200, 0, 0), insets); + } + + @Test + public void testCalculateVisibleInsets_invisible() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisibleFrame(new Rect(0, 0, 500, 200)); + mSource.setVisible(false); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 0, 0, 0), insets); + } + + // Parcel and equals already tested via InsetsStateTest } diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index fa2ffccaaa63..4b76fee00496 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -18,12 +18,14 @@ package android.view; import static android.view.InsetsState.ISIDE_BOTTOM; import static android.view.InsetsState.ISIDE_TOP; +import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.WindowInsets.Type.ime; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; @@ -209,6 +211,42 @@ public class InsetsStateTest { assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME)); } + @Test + public void testCalculateVisibleInsets() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(ITYPE_IME).setVisible(true); + + // Make sure bottom gestures are ignored + mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN); + assertEquals(new Rect(0, 100, 0, 100), visibleInsets); + } + } + + @Test + public void testCalculateVisibleInsets_adjustNothing() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(ITYPE_IME).setVisible(true); + + // Make sure bottom gestures are ignored + mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING); + assertEquals(new Rect(0, 100, 0, 0), visibleInsets); + } + } + private void assertEqualsAndHashCode() { assertEquals(mState, mState2); assertEquals(mState.hashCode(), mState2.hashCode()); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 3586216ad421..93f4b5143c57 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -124,6 +124,10 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void setSoftKeyboardCallbackEnabled(boolean enabled) {} + public boolean switchToInputMethod(String imeId) { + return false; + } + public boolean isAccessibilityButtonAvailable() { return false; } diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index 89c237498e5c..8c9b4d071c2d 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -67,6 +67,7 @@ public class EditorCursorDragTest { mOriginalFlagValue = Editor.FLAG_ENABLE_CURSOR_DRAG; Editor.FLAG_ENABLE_CURSOR_DRAG = true; } + @After public void after() throws Throwable { Editor.FLAG_ENABLE_CURSOR_DRAG = mOriginalFlagValue; @@ -356,6 +357,23 @@ public class EditorCursorDragTest { assertFalse(editor.getSelectionController().isCursorBeingModified()); } + @Test // Reproduces b/147366705 + public void testCursorDrag_nonSelectableTextView() throws Throwable { + String text = "Hello world!"; + TextView tv = mActivity.findViewById(R.id.nonselectable_textview); + tv.setText(text); + Editor editor = tv.getEditorForTesting(); + + // Simulate a tap. No error should be thrown. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); + + // Swipe left to right. No error should be thrown. + onView(withId(R.id.nonselectable_textview)).perform( + dragOnText(text.indexOf("llo"), text.indexOf("!"))); + } + private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) { return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); } diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java index b4116681e723..aa55e08dbcdc 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -19,7 +19,6 @@ package android.widget; import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemDisabled; import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemEnabled; import static android.widget.espresso.ContextMenuUtils.assertContextMenuIsNotDisplayed; -import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles; import static android.widget.espresso.DragHandleUtils.onHandleView; import static android.widget.espresso.TextViewActions.mouseClick; import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex; @@ -64,7 +63,6 @@ import org.junit.runner.RunWith; */ @RunWith(AndroidJUnit4.class) @MediumTest -@Suppress // Consistently failing. b/29591177 public class TextViewActivityMouseTest { @Rule @@ -86,11 +84,8 @@ public class TextViewActivityMouseTest { onView(withId(R.id.textview)).perform(mouseClick()); onView(withId(R.id.textview)).perform(replaceText(helloWorld)); - assertNoSelectionHandles(); - onView(withId(R.id.textview)).perform( mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!"))); - onView(withId(R.id.textview)).check(hasSelection("llo wor")); onHandleView(com.android.internal.R.id.selection_start_handle) @@ -100,8 +95,6 @@ public class TextViewActivityMouseTest { onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w"))); onView(withId(R.id.textview)).check(hasSelection("")); - - assertNoSelectionHandles(); } @Test @@ -196,11 +189,11 @@ public class TextViewActivityMouseTest { onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); onView(withId(R.id.textview)).check(hasSelection("")); - assertNoSelectionHandles(); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); } @Test + @Suppress // Consistently failing. b/29591177 public void testDragAndDrop_longClick() { final String text = "abc def ghi."; onView(withId(R.id.textview)).perform(mouseClick()); @@ -213,7 +206,6 @@ public class TextViewActivityMouseTest { onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); onView(withId(R.id.textview)).check(hasSelection("")); - assertNoSelectionHandles(); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); } diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java index abee7369414b..1928d2587f70 100644 --- a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java +++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java @@ -70,6 +70,8 @@ public final class MouseUiController implements UiController { event.setSource(InputDevice.SOURCE_MOUSE); if (event.getActionMasked() != MotionEvent.ACTION_UP) { event.setButtonState(mButton); + } else { + event.setButtonState(0); } return mUiController.injectMotionEvent(event); } diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING new file mode 100644 index 000000000000..54dd4310bd6e --- /dev/null +++ b/core/tests/overlaytests/remount/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name" : "OverlayRemountedTest" + } + ] +}
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp new file mode 100644 index 000000000000..3825c55f3b4b --- /dev/null +++ b/core/tests/overlaytests/remount/host/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_test_host { + name: "OverlayRemountedTest", + srcs: ["src/**/*.java"], + libs: [ + "tradefed", + "junit", + ], + test_suites: ["general-tests"], + java_resources: [ + ":OverlayRemountedTest_SharedLibrary", + ":OverlayRemountedTest_SharedLibraryOverlay", + ":OverlayRemountedTest_Target", + ], +} diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml new file mode 100644 index 000000000000..11eadf1a4659 --- /dev/null +++ b/core/tests/overlaytests/remount/host/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<configuration description="Test module config for OverlayRemountedTest"> + <option name="test-tag" value="OverlayRemountedTest" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="remount" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.HostTest"> + <option name="jar" value="OverlayRemountedTest.jar" /> + </test> +</configuration> diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java new file mode 100644 index 000000000000..84af18710fe6 --- /dev/null +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Rule; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OverlayHostTest extends BaseHostJUnit4Test { + private static final long TIME_OUT_MS = 30000; + private static final String RES_INSTRUMENTATION_ARG = "res"; + private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays"; + private static final String RESOURCES_TYPE_SUFFIX = "_type"; + private static final String RESOURCES_DATA_SUFFIX = "_data"; + + public final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice); + + @Rule + public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer); + private Map<String, String> mLastResults; + + /** + * Retrieves the values of the resources in the test package. The test package must use the + * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation. + **/ + void retrieveResource(String testPackageName, List<String> requiredOverlayPaths, + String... resourceNames) throws DeviceNotAvailableException { + final HashMap<String, String> args = new HashMap<>(); + if (!requiredOverlayPaths.isEmpty()) { + // Enclose the require overlay paths in quotes so the arguments will be string arguments + // rather than file arguments. + args.put(OVERLAY_INSTRUMENTATION_ARG, + String.format("\"%s\"", String.join(" ", requiredOverlayPaths))); + } + + if (resourceNames.length == 0) { + throw new IllegalArgumentException("Must specify at least one resource to retrieve."); + } + + // Pass the names of the resources to retrieve into the test as one string. + args.put(RES_INSTRUMENTATION_ARG, + String.format("\"%s\"", String.join(" ", resourceNames))); + + runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS, + TIME_OUT_MS, TIME_OUT_MS, false, false, args); + + // Retrieve the results of the most recently run test. + mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null : + getLastDeviceRunResults().getRunMetrics(); + } + + /** Returns the base resource directories of the specified packages. */ + List<String> getPackagePaths(String... packageNames) + throws DeviceNotAvailableException { + final ArrayList<String> paths = new ArrayList<>(); + for (String packageName : packageNames) { + // Use the package manager shell command to find the path of the package. + final String result = getDevice().executeShellCommand( + String.format("pm dump %s | grep \"resourcePath=\"", packageName)); + assertNotNull("Failed to find path for package " + packageName, result); + int splitIndex = result.indexOf('='); + assertTrue(splitIndex >= 0); + paths.add(result.substring(splitIndex + 1).trim()); + } + return paths; + } + + /** Builds the full name of a resource in the form package:type/entry. */ + String resourceName(String pkg, String type, String entry) { + return String.format("%s:%s/%s", pkg, type, entry); + } + + /** + * Asserts that the type and data of a a previously retrieved is the same as expected. + * @param resourceName the full name of the resource in the form package:type/entry + * @param type the expected {@link android.util.TypedValue} type of the resource + * @param data the expected value of the resource when coerced to a string using + * {@link android.util.TypedValue#coerceToString()} + **/ + void assertResource(String resourceName, int type, String data) { + assertNotNull("Failed to get test results", mLastResults); + assertNotEquals("No resource values were retrieved", mLastResults.size(), 0); + assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX)); + assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX)); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java new file mode 100644 index 000000000000..4939e160612e --- /dev/null +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.List; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class OverlaySharedLibraryTest extends OverlayHostTest { + private static final String TARGET_APK = "OverlayRemountedTest_Target.apk"; + private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target"; + private static final String SHARED_LIBRARY_APK = + "OverlayRemountedTest_SharedLibrary.apk"; + private static final String SHARED_LIBRARY_PACKAGE = + "com.android.overlaytest.remounted.shared_library"; + private static final String SHARED_LIBRARY_OVERLAY_APK = + "OverlayRemountedTest_SharedLibraryOverlay.apk"; + private static final String SHARED_LIBRARY_OVERLAY_PACKAGE = + "com.android.overlaytest.remounted.shared_library.overlay"; + + @Test + public void testSharedLibrary() throws Exception { + final String targetResource = resourceName(TARGET_PACKAGE, "bool", + "uses_shared_library_overlaid"); + final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", + "shared_library_overlaid"); + + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) + .reboot() + .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false) + .installResourceApk(TARGET_APK, TARGET_PACKAGE); + + // The shared library resource is not currently overlaid. + retrieveResource(Collections.emptyList(), targetResource, libraryResource); + assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false"); + assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false"); + + // Overlay the shared library resource. + mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); + retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource, + libraryResource); + assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + } + + @Test + public void testSharedLibraryPreEnabled() throws Exception { + final String targetResource = resourceName(TARGET_PACKAGE, "bool", + "uses_shared_library_overlaid"); + final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", + "shared_library_overlaid"); + + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) + .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true) + .reboot() + .installResourceApk(TARGET_APK, TARGET_PACKAGE); + + retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource, + libraryResource); + assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + } + + private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames) + throws DeviceNotAvailableException { + retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java new file mode 100644 index 000000000000..7028f2f1d554 --- /dev/null +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import static org.junit.Assert.assertTrue; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; + +import org.junit.Assert; +import org.junit.rules.ExternalResource; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeoutException; + +class SystemPreparer extends ExternalResource { + private static final long REBOOT_SLEEP_MS = 30000; + private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000; + + // The paths of the files pushed onto the device through this rule. + private ArrayList<String> mPushedFiles = new ArrayList<>(); + + // The package names of packages installed through this rule. + private ArrayList<String> mInstalledPackages = new ArrayList<>(); + + private final TemporaryFolder mHostTempFolder; + private final DeviceProvider mDeviceProvider; + + SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) { + mHostTempFolder = hostTempFolder; + mDeviceProvider = deviceProvider; + } + + /** Copies a file within the host test jar to a path on device. */ + SystemPreparer pushResourceFile(String resourcePath, + String outputPath) throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath)); + mPushedFiles.add(outputPath); + return this; + } + + /** Installs an APK within the host test jar onto the device. */ + SystemPreparer installResourceApk(String resourcePath, String packageName) + throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + final File tmpFile = copyResourceToTemp(resourcePath); + final String result = device.installPackage(tmpFile, true); + Assert.assertNull(result); + mInstalledPackages.add(packageName); + return this; + } + + /** Sets the enable state of an overlay pacakage. */ + SystemPreparer setOverlayEnabled(String packageName, boolean enabled) + throws ExecutionException, TimeoutException { + final ITestDevice device = mDeviceProvider.getDevice(); + + // Wait for the overlay to change its enabled state. + final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> { + while (true) { + device.executeShellCommand(String.format("cmd overlay %s %s", + enabled ? "enable" : "disable", packageName)); + + final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName; + if (device.executeShellCommand("cmd overlay list").contains(pattern)) { + return true; + } + } + }); + + final Executor executor = (cmd) -> new Thread(cmd).start(); + executor.execute(enabledListener); + try { + enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS); + } catch (InterruptedException ignored) { + } + + return this; + } + + /** Restarts the device and waits until after boot is completed. */ + SystemPreparer reboot() throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + device.executeShellCommand("stop"); + device.executeShellCommand("start"); + try { + // Sleep until the device is ready for test execution. + Thread.sleep(REBOOT_SLEEP_MS); + } catch (InterruptedException ignored) { + } + + return this; + } + + /** Copies a file within the host test jar to a temporary file on the host machine. */ + private File copyResourceToTemp(String resourcePath) throws IOException { + final File tempFile = mHostTempFolder.newFile(resourcePath); + final ClassLoader classLoader = getClass().getClassLoader(); + try (InputStream assetIs = classLoader.getResource(resourcePath).openStream(); + FileOutputStream assetOs = new FileOutputStream(tempFile)) { + if (assetIs == null) { + throw new IllegalStateException("Failed to find resource " + resourcePath); + } + + int b; + while ((b = assetIs.read()) >= 0) { + assetOs.write(b); + } + } + + return tempFile; + } + + /** Removes installed packages and files that were pushed to the device. */ + @Override + protected void after() { + final ITestDevice device = mDeviceProvider.getDevice(); + try { + for (final String file : mPushedFiles) { + device.deleteFile(file); + } + for (final String packageName : mInstalledPackages) { + device.uninstallPackage(packageName); + } + } catch (DeviceNotAvailableException e) { + Assert.fail(e.toString()); + } + } + + interface DeviceProvider { + ITestDevice getDevice(); + } +} diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp new file mode 100644 index 000000000000..ffb0572c3fd0 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp @@ -0,0 +1,19 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "OverlayRemountedTest_SharedLibrary", + sdk_version: "current", + aaptflags: ["--shared-lib"], +} diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml new file mode 100644 index 000000000000..06e3f6a99410 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.shared_library"> + <application> + <library android:name="com.android.overlaytest.remounted.shared_library" /> + </application> +</manifest>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml new file mode 100644 index 000000000000..1b06f6d7530b --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <overlayable name="TestResources"> + <policy type="public"> + <item type="bool" name="shared_library_overlaid" /> + </policy> + </overlayable> +</resources> diff --git a/core/res/res/interpolator/screen_rotation_alpha_out.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml index 73a37d4f1aa5..5b9db163a274 100644 --- a/core/res/res/interpolator/screen_rotation_alpha_out.xml +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml @@ -15,8 +15,6 @@ ~ limitations under the License. --> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:controlX1="0.57" - android:controlY1="0" - android:controlX2="0.71" - android:controlY2=".43"/> +<resources> + <public type="bool" name="shared_library_overlaid" id="0x00050001"/> +</resources>
\ No newline at end of file diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml index 9c566a7c8f23..2dc47a7ecf61 100644 --- a/core/res/res/interpolator/screen_rotation_alpha_in.xml +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml @@ -15,8 +15,6 @@ ~ limitations under the License. --> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:controlX1="0.15" - android:controlY1="0.45" - android:controlX2="0.33" - android:controlY2="1"/> +<resources> + <bool name="shared_library_overlaid">false</bool> +</resources> diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp new file mode 100644 index 000000000000..0d29aec909d5 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp @@ -0,0 +1,18 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "OverlayRemountedTest_SharedLibraryOverlay", + sdk_version: "current", +} diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..53a4e61949da --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.shared_library.overlay"> + <application android:hasCode="false" /> + <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library" + android:targetName="TestResources" /> +</manifest>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml new file mode 100644 index 000000000000..f66448a8d991 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <bool name="shared_library_overlaid">true</bool> +</resources> diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp new file mode 100644 index 000000000000..83f9f28b3f48 --- /dev/null +++ b/core/tests/overlaytests/remount/target/Android.bp @@ -0,0 +1,20 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "OverlayRemountedTest_Target", + srcs: ["src/**/*.java"], + sdk_version: "test_current", + libs: ["OverlayRemountedTest_SharedLibrary"], +} diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml new file mode 100644 index 000000000000..32fec43593f8 --- /dev/null +++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.target"> + + <application> + <uses-library android:name="android.test.runner" /> + <uses-library android:name="com.android.overlaytest.remounted.shared_library" + android:required="true" /> + </application> + + <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner" + android:targetPackage="com.android.overlaytest.remounted.target" + android:label="Remounted system RRO tests" /> +</manifest> diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/target/res/values/values.xml new file mode 100644 index 000000000000..b5f444a5eb72 --- /dev/null +++ b/core/tests/overlaytests/remount/target/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library"> + <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool> +</resources> diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java new file mode 100644 index 000000000000..2e4c211d6a0a --- /dev/null +++ b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted.target; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.res.Resources; +import android.os.Bundle; +import android.util.Log; +import android.util.TypedValue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + +/** + * An {@link Instrumentation} that retrieves the value of specified resources within the + * application. + **/ +public class ResourceRetrievalRunner extends Instrumentation { + private static final String TAG = ResourceRetrievalRunner.class.getSimpleName(); + + // A list of whitespace separated resource names of which to retrieve the resource values. + private static final String RESOURCE_LIST_TAG = "res"; + + // A list of whitespace separated overlay package paths that must be present before retrieving + // resource values. + private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays"; + + // The suffixes of the keys returned from the instrumentation. To retrieve the type of a + // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix + // to the end of the name of the resource. For the value of a resource, use + // {@link #RESOURCES_DATA_SUFFIX} instead. + private static final String RESOURCES_TYPE_SUFFIX = "_type"; + private static final String RESOURCES_DATA_SUFFIX = "_data"; + + // The amount of time in seconds to wait for the overlays to be present in the AssetManager. + private static final int OVERLAY_PATH_TIMEOUT = 60; + + private final ArrayList<String> mResourceNames = new ArrayList<>(); + private final ArrayList<String> mOverlayPaths = new ArrayList<>(); + private final Bundle mResult = new Bundle(); + + /** + * Receives the instrumentation arguments and runs the resource retrieval. + * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a + * whitespace separated string of resource names of which to retrieve the resource values. + * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a + * whitespace separated string of overlay package paths prefixes that must be present before + * retrieving the resource values. + */ + @Override + public void onCreate(Bundle arguments) { + super.onCreate(arguments); + mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" "))); + if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) { + mOverlayPaths.addAll(Arrays.asList( + arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" "))); + } + start(); + } + + @Override + public void onStart() { + final Resources res = getContext().getResources(); + res.getAssets().setResourceResolutionLoggingEnabled(true); + + if (!mOverlayPaths.isEmpty()) { + Log.d(TAG, String.format("Waiting for overlay paths [%s]", + String.join(",", mOverlayPaths))); + + // Wait for all required overlays to be present in the AssetManager. + final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> { + while (!mOverlayPaths.isEmpty()) { + final String[] apkPaths = res.getAssets().getApkPaths(); + for (String path : apkPaths) { + for (String overlayPath : mOverlayPaths) { + if (path.startsWith(overlayPath)) { + mOverlayPaths.remove(overlayPath); + break; + } + } + } + } + return true; + }); + + try { + final Executor executor = (t) -> new Thread(t).start(); + executor.execute(overlayListener); + overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, String.format("Failed to wait for required overlays [%s]", + String.join(",", mOverlayPaths)), e); + finish(Activity.RESULT_CANCELED, mResult); + } + } + + // Retrieve the values for each resource passed in. + final TypedValue typedValue = new TypedValue(); + for (final String resourceName : mResourceNames) { + try { + final int resId = res.getIdentifier(resourceName, null, null); + res.getValue(resId, typedValue, true); + Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId), + res.getAssets().getLastResourceResolution())); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e); + finish(Activity.RESULT_CANCELED, mResult); + } + + putValue(resourceName, typedValue); + } + + finish(Activity.RESULT_OK, mResult); + } + + private void putValue(String resourceName, TypedValue value) { + mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type); + final CharSequence textValue = value.coerceToString(); + mResult.putString(resourceName + RESOURCES_DATA_SUFFIX, + textValue == null ? "null" : textValue.toString()); + } +} diff --git a/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java new file mode 100644 index 000000000000..0809f693ddd5 --- /dev/null +++ b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.HashMap; + +/** Unit tests for {@link ConnectivityUtil}. */ +public class ConnectivityUtilTest { + + public static final String TAG = "ConnectivityUtilTest"; + + // Mock objects for testing + @Mock private Context mMockContext; + @Mock private PackageManager mMockPkgMgr; + @Mock private ApplicationInfo mMockApplInfo; + @Mock private AppOpsManager mMockAppOps; + @Mock private UserManager mMockUserManager; + @Mock private LocationManager mLocationManager; + + private static final String TEST_PKG_NAME = "com.google.somePackage"; + private static final String TEST_FEATURE_ID = "com.google.someFeature"; + private static final int MANAGED_PROFILE_UID = 1100000; + private static final int OTHER_USER_UID = 1200000; + + private final String mInteractAcrossUsersFullPermission = + "android.permission.INTERACT_ACROSS_USERS_FULL"; + private final String mManifestStringCoarse = + Manifest.permission.ACCESS_COARSE_LOCATION; + private final String mManifestStringFine = + Manifest.permission.ACCESS_FINE_LOCATION; + + // Test variables + private int mWifiScanAllowApps; + private int mUid; + private int mCoarseLocationPermission; + private int mAllowCoarseLocationApps; + private int mFineLocationPermission; + private int mAllowFineLocationApps; + private int mCurrentUser; + private boolean mIsLocationEnabled; + private boolean mThrowSecurityException; + private Answer<Integer> mReturnPermission; + private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>(); + + private class TestConnectivityUtil extends ConnectivityUtil { + + TestConnectivityUtil(Context context) { + super(context); + } + + @Override + protected int getCurrentUser() { + return mCurrentUser; + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + initTestVars(); + } + + private void setupMocks() throws Exception { + when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any())) + .thenReturn(mMockApplInfo); + when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr); + when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME, + TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps); + when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid), + eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) + .thenReturn(mAllowCoarseLocationApps); + when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid), + eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) + .thenReturn(mAllowFineLocationApps); + if (mThrowSecurityException) { + doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong" + + " to application bound to user " + mUid)) + .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME); + } + when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)) + .thenReturn(mMockAppOps); + when(mMockContext.getSystemService(Context.USER_SERVICE)) + .thenReturn(mMockUserManager); + when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager); + } + + private void setupTestCase() throws Exception { + setupMocks(); + setupMockInterface(); + } + + private void initTestVars() { + mPermissionsList.clear(); + mReturnPermission = createPermissionAnswer(); + mWifiScanAllowApps = AppOpsManager.MODE_ERRORED; + mUid = OTHER_USER_UID; + mThrowSecurityException = true; + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M; + mIsLocationEnabled = false; + mCurrentUser = UserHandle.USER_SYSTEM; + mCoarseLocationPermission = PackageManager.PERMISSION_DENIED; + mFineLocationPermission = PackageManager.PERMISSION_DENIED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED; + mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; + } + + private void setupMockInterface() { + Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid()); + doAnswer(mReturnPermission).when(mMockContext).checkPermission( + anyString(), anyInt(), anyInt()); + when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM, + UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID))) + .thenReturn(true); + when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid)) + .thenReturn(mCoarseLocationPermission); + when(mMockContext.checkPermission(mManifestStringFine, -1, mUid)) + .thenReturn(mFineLocationPermission); + when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled); + } + + private Answer<Integer> createPermissionAnswer() { + return new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) { + int myUid = (int) invocation.getArguments()[1]; + String myPermission = (String) invocation.getArguments()[0]; + mPermissionsList.get(myPermission); + if (mPermissionsList.containsKey(myPermission)) { + int uid = mPermissionsList.get(myPermission); + if (myUid == uid) { + return PackageManager.PERMISSION_GRANTED; + } + } + return PackageManager.PERMISSION_DENIED; + } + }; + } + + @Test + public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception { + mIsLocationEnabled = true; + mThrowSecurityException = false; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + mUid = mCurrentUser; + setupTestCase(); + new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + } + + @Test + public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception { + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; + mIsLocationEnabled = true; + mThrowSecurityException = false; + mUid = mCurrentUser; + mFineLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + } + + @Test + public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception { + mThrowSecurityException = true; + mIsLocationEnabled = true; + mFineLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + @Test + public void testenforceCanAccessScanResults_UserOrProfileNotCurrent() throws Exception { + mIsLocationEnabled = true; + mThrowSecurityException = false; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + @Test + public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception { + mThrowSecurityException = false; + mIsLocationEnabled = true; + setupTestCase(); + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + @Test + public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception { + mThrowSecurityException = false; + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; + mIsLocationEnabled = true; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; + mUid = MANAGED_PROFILE_UID; + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString()); + } + + @Test + public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception { + mThrowSecurityException = false; + mUid = MANAGED_PROFILE_UID; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid); + mIsLocationEnabled = false; + + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) { + try { + r.run(); + Assert.fail("Expected " + exceptionClass + " to be thrown."); + } catch (Exception exception) { + assertTrue(exceptionClass.isInstance(exception)); + } + } +} diff --git a/data/etc/OWNERS b/data/etc/OWNERS index ea66ee373785..70d467829269 100644 --- a/data/etc/OWNERS +++ b/data/etc/OWNERS @@ -1 +1 @@ -per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com +per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com, lorenzo@google.com diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 87657191e901..3f2f3495ae8f 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -44,6 +44,7 @@ cc_library { "AttributeResolution.cpp", "ChunkIterator.cpp", "ConfigDescription.cpp", + "DynamicLibManager.cpp", "Idmap.cpp", "LoadedArsc.cpp", "Locale.cpp", diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index ca4143f3e215..8cfd2d8ca696 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -25,6 +25,7 @@ #include "android-base/logging.h" #include "android-base/stringprintf.h" +#include "androidfw/DynamicLibManager.h" #include "androidfw/ResourceUtils.h" #include "androidfw/Util.h" #include "utils/ByteOrder.h" @@ -66,7 +67,12 @@ struct FindEntryResult { StringPoolRef entry_string_ref; }; -AssetManager2::AssetManager2() { +AssetManager2::AssetManager2() : dynamic_lib_manager_(std::make_unique<DynamicLibManager>()) { + memset(&configuration_, 0, sizeof(configuration_)); +} + +AssetManager2::AssetManager2(DynamicLibManager* dynamic_lib_manager) + : dynamic_lib_manager_(dynamic_lib_manager) { memset(&configuration_, 0, sizeof(configuration_)); } @@ -85,24 +91,44 @@ 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. + // 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. + std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_); + std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) { + return !a->IsOverlay(); + }); + std::unordered_map<std::string, uint8_t> apk_assets_package_ids; + std::unordered_map<std::string, uint8_t> package_name_package_ids; + + // Assign stable package ids to application packages. + uint8_t next_available_package_id = 0U; + for (const auto& apk_assets : sorted_apk_assets) { + for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) { + uint8_t package_id = package->GetPackageId(); + if (package->IsOverlay()) { + package_id = GetDynamicLibManager()->FindUnassignedId(next_available_package_id); + next_available_package_id = package_id + 1; + } else if (package->IsDynamic()) { + package_id = GetDynamicLibManager()->GetAssignedId(package->GetPackageName()); + } - // 0x01 is reserved for the android package. - int next_package_id = 0x02; - const size_t apk_assets_count = apk_assets_.size(); - for (size_t i = 0; i < apk_assets_count; i++) { - const ApkAssets* apk_assets = apk_assets_[i]; - const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc(); + // Map the path of the apk assets to the package id of its first loaded package. + apk_assets_package_ids[apk_assets->GetPath()] = package_id; - for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) { - // Get the package ID or assign one if a shared library. - int package_id; - if (package->IsDynamic()) { - package_id = next_package_id++; - } else { - package_id = package->GetPackageId(); - } + // Map the package name of the package to the first loaded package with that package id. + package_name_package_ids[package->GetPackageName()] = package_id; + } + } + + const int apk_assets_count = apk_assets_.size(); + for (int i = 0; i < apk_assets_count; i++) { + const auto& apk_assets = apk_assets_[i]; + for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) { + const auto package_id_entry = package_name_package_ids.find(package->GetPackageName()); + CHECK(package_id_entry != package_name_package_ids.end()) + << "no package id assgined to package " << package->GetPackageName(); + const uint8_t package_id = package_id_entry->second; // Add the mapping for package ID to index if not present. uint8_t idx = package_ids_[package_id]; @@ -115,7 +141,10 @@ void AssetManager2::BuildDynamicRefTable() { // to take effect. const auto& loaded_idmap = apk_assets->GetLoadedIdmap(); auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath()); - if (target_package_iter != apk_assets_package_ids.end()) { + if (target_package_iter == apk_assets_package_ids.end()) { + LOG(INFO) << "failed to find target package for overlay " + << loaded_idmap->OverlayApkPath(); + } else { const uint8_t target_package_id = target_package_iter->second; const uint8_t target_idx = package_ids_[target_package_id]; CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not" @@ -123,7 +152,7 @@ void AssetManager2::BuildDynamicRefTable() { PackageGroup& target_package_group = package_groups_[target_idx]; - // Create a special dynamic reference table for the overlay to rewite references to + // Create a special dynamic reference table for the overlay to rewrite references to // overlay resources as references to the target resources they overlay. auto overlay_table = std::make_shared<OverlayDynamicRefTable>( loaded_idmap->GetOverlayDynamicRefTable(target_package_id)); @@ -153,8 +182,6 @@ void AssetManager2::BuildDynamicRefTable() { package_group->dynamic_ref_table->mEntries.replaceValueFor( package_name, static_cast<uint8_t>(entry.package_id)); } - - apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id)); } } @@ -567,7 +594,7 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri if (resource_resolution_logging_enabled_) { last_resolution_.steps.push_back( Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(), - &package_group.packages_[0].loaded_package_->GetPackageName()}); + overlay_result.package_name}); } } } @@ -1279,6 +1306,16 @@ uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const return 0; } +DynamicLibManager* AssetManager2::GetDynamicLibManager() const { + auto dynamic_lib_manager = + std::get_if<std::unique_ptr<DynamicLibManager>>(&dynamic_lib_manager_); + if (dynamic_lib_manager) { + return (*dynamic_lib_manager).get(); + } else { + return *std::get_if<DynamicLibManager*>(&dynamic_lib_manager_); + } +} + std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); } diff --git a/libs/androidfw/DynamicLibManager.cpp b/libs/androidfw/DynamicLibManager.cpp new file mode 100644 index 000000000000..895b7695bf26 --- /dev/null +++ b/libs/androidfw/DynamicLibManager.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "androidfw/DynamicLibManager.h" + +namespace android { + +uint8_t DynamicLibManager::GetAssignedId(const std::string& library_package_name) { + auto lib_entry = shared_lib_package_ids_.find(library_package_name); + if (lib_entry != shared_lib_package_ids_.end()) { + return lib_entry->second; + } + + return shared_lib_package_ids_[library_package_name] = next_package_id_++; +} + +uint8_t DynamicLibManager::FindUnassignedId(uint8_t start_package_id) { + return (start_package_id < next_package_id_) ? next_package_id_ : start_package_id; +} + +} // namespace android diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 00cbbcad56e6..b2cec2a42994 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -27,6 +27,7 @@ #include "androidfw/ApkAssets.h" #include "androidfw/Asset.h" #include "androidfw/AssetManager.h" +#include "androidfw/DynamicLibManager.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" @@ -94,6 +95,7 @@ class AssetManager2 { }; AssetManager2(); + explicit AssetManager2(DynamicLibManager* dynamic_lib_manager); // Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets // are not owned by the AssetManager, and must have a longer lifetime. @@ -371,6 +373,8 @@ class AssetManager2 { // Retrieve the assigned package id of the package if loaded into this AssetManager uint8_t GetAssignedPackageId(const LoadedPackage* package) const; + DynamicLibManager* GetDynamicLibManager() const; + // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must // have a longer lifetime. std::vector<const ApkAssets*> apk_assets_; @@ -389,6 +393,9 @@ class AssetManager2 { // may need to be purged. ResTable_config configuration_; + // Component responsible for assigning package ids to shared libraries. + std::variant<std::unique_ptr<DynamicLibManager>, DynamicLibManager*> dynamic_lib_manager_; + // Cached set of bags. These are cached because they can inherit keys from parent bags, // which involves some calculation. std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_; diff --git a/libs/androidfw/include/androidfw/DynamicLibManager.h b/libs/androidfw/include/androidfw/DynamicLibManager.h new file mode 100644 index 000000000000..1ff7079573d2 --- /dev/null +++ b/libs/androidfw/include/androidfw/DynamicLibManager.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROIDFW_DYNAMICLIBMANAGER_H +#define ANDROIDFW_DYNAMICLIBMANAGER_H + +#include <string> +#include <unordered_map> + +#include "android-base/macros.h" + +namespace android { + +// Manages assigning resource ids for dynamic resources. +class DynamicLibManager { + public: + DynamicLibManager() = default; + + // Retrieves the assigned package id for the library. + uint8_t GetAssignedId(const std::string& library_package_name); + + // Queries in ascending order for the first available package id that is not currently assigned to + // a library. + uint8_t FindUnassignedId(uint8_t start_package_id); + + private: + DISALLOW_COPY_AND_ASSIGN(DynamicLibManager); + + uint8_t next_package_id_ = 0x02; + std::unordered_map<std::string, uint8_t> shared_lib_package_ids_; +}; + +} // namespace android + +#endif //ANDROIDFW_DYNAMICLIBMANAGER_H diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h index 64924f433245..8891512958f0 100644 --- a/libs/androidfw/include/androidfw/MutexGuard.h +++ b/libs/androidfw/include/androidfw/MutexGuard.h @@ -47,7 +47,8 @@ class Guarded { static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer"); public: - explicit Guarded() : guarded_() { + template <typename ...Args> + explicit Guarded(Args&& ...args) : guarded_(std::forward<Args>(args)...) { } template <typename U = T> diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index b3190be8caf1..2f6f3dfcaf1c 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -214,6 +214,25 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); } +TEST_F(AssetManager2Test, AssignsUnchangingPackageIdToSharedLibrary) { + DynamicLibManager lib_manager; + AssetManager2 assetmanager(&lib_manager); + assetmanager.SetApkAssets( + {lib_one_assets_.get(), lib_two_assets_.get(), libclient_assets_.get()}); + + AssetManager2 assetmanager2(&lib_manager); + assetmanager2.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + + uint32_t res_id = assetmanager.GetResourceId("com.android.lib_one:string/foo"); + ASSERT_NE(0U, res_id); + + uint32_t res_id_2 = assetmanager2.GetResourceId("com.android.lib_one:string/foo"); + ASSERT_NE(0U, res_id_2); + + ASSERT_EQ(res_id, res_id_2); +} + TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { AssetManager2 assetmanager; assetmanager.SetApkAssets({lib_one_assets_.get()}); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d945fc49ec88..a7f8cc4688ef 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -215,6 +215,7 @@ cc_defaults { android: { srcs: [ + "pipeline/skia/ATraceMemoryDump.cpp", "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", "pipeline/skia/ShaderCache.cpp", @@ -244,7 +245,6 @@ cc_defaults { "DeviceInfo.cpp", "FrameInfo.cpp", "FrameInfoVisualizer.cpp", - "GpuMemoryTracker.cpp", "HardwareBitmapUploader.cpp", "HWUIProperties.sysprop", "JankTracker.cpp", @@ -325,7 +325,6 @@ cc_test { "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", "tests/unit/FatVectorTests.cpp", - "tests/unit/GpuMemoryTrackerTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", "tests/unit/LayerUpdateQueueTests.cpp", "tests/unit/LinearAllocatorTests.cpp", diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp deleted file mode 100644 index a9a7af8f22f3..000000000000 --- a/libs/hwui/GpuMemoryTracker.cpp +++ /dev/null @@ -1,122 +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. - */ - -#include "utils/StringUtils.h" - -#include <GpuMemoryTracker.h> -#include <cutils/compiler.h> -#include <utils/Trace.h> -#include <array> -#include <sstream> -#include <unordered_set> -#include <vector> - -namespace android { -namespace uirenderer { - -pthread_t gGpuThread = 0; - -#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount) - -const char* TYPE_NAMES[] = { - "Texture", "OffscreenBuffer", "Layer", -}; - -struct TypeStats { - int totalSize = 0; - int count = 0; -}; - -static std::array<TypeStats, NUM_TYPES> gObjectStats; -static std::unordered_set<GpuMemoryTracker*> gObjectSet; - -void GpuMemoryTracker::notifySizeChanged(int newSize) { - int delta = newSize - mSize; - mSize = newSize; - gObjectStats[static_cast<int>(mType)].totalSize += delta; -} - -void GpuMemoryTracker::startTrackingObject() { - auto result = gObjectSet.insert(this); - LOG_ALWAYS_FATAL_IF(!result.second, - "startTrackingObject() on %p failed, already being tracked!", this); - gObjectStats[static_cast<int>(mType)].count++; -} - -void GpuMemoryTracker::stopTrackingObject() { - size_t removed = gObjectSet.erase(this); - LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?", - removed, this); - gObjectStats[static_cast<int>(mType)].count--; -} - -void GpuMemoryTracker::onGpuContextCreated() { - LOG_ALWAYS_FATAL_IF(gGpuThread != 0, - "We already have a gpu thread? " - "current = %lu, gpu thread = %lu", - pthread_self(), gGpuThread); - gGpuThread = pthread_self(); -} - -void GpuMemoryTracker::onGpuContextDestroyed() { - gGpuThread = 0; - if (CC_UNLIKELY(gObjectSet.size() > 0)) { - std::stringstream os; - dump(os); - ALOGE("%s", os.str().c_str()); - LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size()); - } -} - -void GpuMemoryTracker::dump() { - std::stringstream strout; - dump(strout); - ALOGD("%s", strout.str().c_str()); -} - -void GpuMemoryTracker::dump(std::ostream& stream) { - for (int type = 0; type < NUM_TYPES; type++) { - const TypeStats& stats = gObjectStats[type]; - stream << TYPE_NAMES[type]; - stream << " is using " << SizePrinter{stats.totalSize}; - stream << ", count = " << stats.count; - stream << std::endl; - } -} - -int GpuMemoryTracker::getInstanceCount(GpuObjectType type) { - return gObjectStats[static_cast<int>(type)].count; -} - -int GpuMemoryTracker::getTotalSize(GpuObjectType type) { - return gObjectStats[static_cast<int>(type)].totalSize; -} - -void GpuMemoryTracker::onFrameCompleted() { - if (ATRACE_ENABLED()) { - char buf[128]; - for (int type = 0; type < NUM_TYPES; type++) { - snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]); - const TypeStats& stats = gObjectStats[type]; - ATRACE_INT(buf, stats.totalSize); - snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]); - ATRACE_INT(buf, stats.count); - } - } -} - -} // namespace uirenderer -} // namespace android; diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h deleted file mode 100644 index de3ca99ef14b..000000000000 --- a/libs/hwui/GpuMemoryTracker.h +++ /dev/null @@ -1,77 +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. - */ -#pragma once - -#include <pthread.h> -#include <ostream> - -#include <log/log.h> - -namespace android { -namespace uirenderer { - -extern pthread_t gGpuThread; - -#define ASSERT_GPU_THREAD() \ - LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()), \ - "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \ - "!= gpu thread %lu", \ - this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread) - -enum class GpuObjectType { - Texture = 0, - OffscreenBuffer, - Layer, - - TypeCount, -}; - -class GpuMemoryTracker { -public: - GpuObjectType objectType() { return mType; } - int objectSize() { return mSize; } - - static void onGpuContextCreated(); - static void onGpuContextDestroyed(); - static void dump(); - static void dump(std::ostream& stream); - static int getInstanceCount(GpuObjectType type); - static int getTotalSize(GpuObjectType type); - static void onFrameCompleted(); - -protected: - explicit GpuMemoryTracker(GpuObjectType type) : mType(type) { - ASSERT_GPU_THREAD(); - startTrackingObject(); - } - - ~GpuMemoryTracker() { - notifySizeChanged(0); - stopTrackingObject(); - } - - void notifySizeChanged(int newSize); - -private: - void startTrackingObject(); - void stopTrackingObject(); - - int mSize = 0; - GpuObjectType mType; -}; - -} // namespace uirenderer -} // namespace android; diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp index 7921662b213c..a8e36e37905d 100644 --- a/libs/hwui/ProfileData.cpp +++ b/libs/hwui/ProfileData.cpp @@ -15,6 +15,7 @@ */ #include "ProfileData.h" +#include "Properties.h" #include <cinttypes> @@ -102,6 +103,7 @@ void ProfileData::mergeWith(const ProfileData& other) { mGPUFrameCounts[i] >>= divider; mGPUFrameCounts[i] += other.mGPUFrameCounts[i]; } + mPipelineType = other.mPipelineType; } void ProfileData::dump(int fd) const { @@ -157,6 +159,7 @@ void ProfileData::reset() { mTotalFrameCount = 0; mJankFrameCount = 0; mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC); + mPipelineType = Properties::getRenderPipelineType(); } void ProfileData::reportFrame(int64_t duration) { diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h index ccbffc6f136e..dd3ba661dd29 100644 --- a/libs/hwui/ProfileData.h +++ b/libs/hwui/ProfileData.h @@ -16,6 +16,7 @@ #pragma once +#include "Properties.h" #include "utils/Macros.h" #include <utils/Timers.h> @@ -65,6 +66,7 @@ public: uint32_t jankFrameCount() const { return mJankFrameCount; } nsecs_t statsStartTime() const { return mStatStartTime; } uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; } + RenderPipelineType pipelineType() const { return mPipelineType; } struct HistogramEntry { uint32_t renderTimeMs; @@ -103,6 +105,9 @@ private: uint32_t mTotalFrameCount; uint32_t mJankFrameCount; nsecs_t mStatStartTime; + + // true if HWUI renders with Vulkan pipeline + RenderPipelineType mPipelineType; }; // For testing diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 23140724ef64..12681ae221d5 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -746,7 +746,10 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai glyphFunc(buffer.glyphs, buffer.pos); sk_sp<SkTextBlob> textBlob(builder.make()); - mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy); + + apply_looper(&paintCopy, [&](const SkPaint& p) { + mCanvas->drawTextBlob(textBlob, 0, 0, p); + }); drawTextDecorations(x, y, totalAdvance, paintCopy); } @@ -783,8 +786,10 @@ void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x(); xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y(); } - - this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy); + auto* finalCanvas = this->asSkCanvas(); + apply_looper(&paintCopy, [&](const SkPaint& p) { + finalCanvas->drawTextBlob(builder.make(), 0, 0, paintCopy); + }); } // ---------------------------------------------------------------------------- diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp new file mode 100644 index 000000000000..2c78b024536b --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#include "ATraceMemoryDump.h" + +#include <utils/Trace.h> + +#include <cstring> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +// When purgeable is INVALID_TIME it won't be logged at all. +#define INVALID_TIME -1 + +/** + * Skia invokes the following SkTraceMemoryDump functions: + * 1. dumpNumericValue (dumpName, units="bytes", valueName="size") + * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not + * invoke dumpStringValue] + * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional] + * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not + * invoke setMemoryBacking] + * + * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to + * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking. + * Only GPU Texture memory is tracked separately and everything else is grouped as one + * "GPU Memory" category. + */ +static std::unordered_map<const char*, const char*> sResourceMap = { + {"malloc", "Graphics CPU Memory"}, // taken from setMemoryBacking(backingType) + {"gl_texture", "Graphics Texture Memory"}, // taken from setMemoryBacking(backingType) + {"Texture", + "Graphics Texture Memory"}, // taken from dumpStringValue(value, valueName="type") + // Uncomment categories below to split "GPU Memory" into more brackets for debugging. + /*{"vk_buffer", "vk_buffer"}, + {"gl_renderbuffer", "gl_renderbuffer"}, + {"gl_buffer", "gl_buffer"}, + {"RenderTarget", "RenderTarget"}, + {"Stencil", "Stencil"}, + {"Path Data", "Path Data"}, + {"Buffer Object", "Buffer Object"}, + {"Surface", "Surface"},*/ +}; + +ATraceMemoryDump::ATraceMemoryDump() { + mLastDumpName.reserve(100); + mCategory.reserve(100); +} + +void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName, + const char* units, uint64_t value) { + if (!strcmp(units, "bytes")) { + recordAndResetCountersIfNeeded(dumpName); + if (!strcmp(valueName, "size")) { + mLastDumpValue = value; + } else if (!strcmp(valueName, "purgeable_size")) { + mLastPurgeableDumpValue = value; + } + } +} + +void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName, + const char* value) { + if (!strcmp(valueName, "type")) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(value); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } + } +} + +void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(backingType); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } +} + +/** + * startFrame is invoked before dumping anything. It resets counters from the previous frame. + * This is important, because if there is no new data for a given category trace would assume + * usage has not changed (instead of reporting 0). + */ +void ATraceMemoryDump::startFrame() { + resetCurrentCounter(""); + for (auto& it : mCurrentValues) { + // Once a category is observed in at least one frame, it is always reported in subsequent + // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not + // changed since the previous frame, which is not what we want. + it.second.time = 0; + // If purgeableTime is INVALID_TIME, then logTraces won't log it at all. + if (it.second.purgeableTime != INVALID_TIME) { + it.second.purgeableTime = 0; + } + } +} + +/** + * logTraces reads from mCurrentValues and logs the counters with ATRACE. + */ +void ATraceMemoryDump::logTraces() { + // Accumulate data from last dumpName + recordAndResetCountersIfNeeded(""); + for (auto& it : mCurrentValues) { + ATRACE_INT64(it.first.c_str(), it.second.time); + if (it.second.purgeableTime != INVALID_TIME) { + ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime); + } + } +} + +/** + * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and + * accumulates in mCurrentValues[category]. It makes provision to create a new category and track + * purgeable memory only if there is at least one observation. + * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName + * is received. + */ +void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) { + if (!mLastDumpName.compare(dumpName)) { + // Still waiting for more data for current dumpName. + return; + } + + // First invocation will have an empty mLastDumpName. + if (!mLastDumpName.empty()) { + // A new dumpName observed -> store the data already collected. + auto memoryCounter = mCurrentValues.find(mCategory); + if (memoryCounter != mCurrentValues.end()) { + memoryCounter->second.time += mLastDumpValue; + if (mLastPurgeableDumpValue != INVALID_TIME) { + if (memoryCounter->second.purgeableTime == INVALID_TIME) { + memoryCounter->second.purgeableTime = mLastPurgeableDumpValue; + } else { + memoryCounter->second.purgeableTime += mLastPurgeableDumpValue; + } + } + } else { + mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue}; + } + } + + // Reset counters and default category for the newly observed "dumpName". + resetCurrentCounter(dumpName); +} + +void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) { + mLastDumpValue = 0; + mLastPurgeableDumpValue = INVALID_TIME; + mLastDumpName = dumpName; + // Categories not listed in sResourceMap are reported as "GPU memory" + mCategory = "GPU Memory"; +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h new file mode 100644 index 000000000000..aa5c401ad1ca --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include <SkString.h> +#include <SkTraceMemoryDump.h> + +#include <string> +#include <unordered_map> +#include <utility> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class ATraceMemoryDump : public SkTraceMemoryDump { +public: + ATraceMemoryDump(); + ~ATraceMemoryDump() override {} + + void dumpNumericValue(const char* dumpName, const char* valueName, const char* units, + uint64_t value) override; + + void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override; + + LevelOfDetail getRequestedDetails() const override { + return SkTraceMemoryDump::kLight_LevelOfDetail; + } + + bool shouldDumpWrappedObjects() const override { return false; } + + void setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) override; + + void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {} + + void startFrame(); + + void logTraces(); + +private: + std::string mLastDumpName; + + uint64_t mLastDumpValue; + + uint64_t mLastPurgeableDumpValue; + + std::string mCategory; + + struct TraceValue { + uint64_t time; + uint64_t purgeableTime; + }; + + // keys are define in sResourceMap + std::unordered_map<std::string, TraceValue> mCurrentValues; + + void recordAndResetCountersIfNeeded(const char* dumpName); + + void resetCurrentCounter(const char* dumpName); +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto index 0cd5c6228504..dd5676c9c961 100644 --- a/libs/hwui/protos/graphicsstats.proto +++ b/libs/hwui/protos/graphicsstats.proto @@ -29,6 +29,11 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } + // The package name of the app optional string package_name = 1; @@ -49,6 +54,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index fad9440be73f..7e8c96d96860 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -16,7 +16,6 @@ #include "renderstate/RenderState.h" #include "renderthread/RenderThread.h" -#include "GpuMemoryTracker.h" namespace android { namespace uirenderer { @@ -25,15 +24,10 @@ RenderState::RenderState(renderthread::RenderThread& thread) : mRenderThread(thr mThreadId = pthread_self(); } -void RenderState::onContextCreated() { - GpuMemoryTracker::onGpuContextCreated(); -} - void RenderState::onContextDestroyed() { for(auto callback : mContextCallbacks) { callback->onContextDestroyed(); } - GpuMemoryTracker::onGpuContextDestroyed(); } void RenderState::postDecStrong(VirtualLightRefBase* object) { diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index ff5d02fe359a..e08d32a7735c 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -62,7 +62,6 @@ private: ~RenderState() {} // Context notifications are only to be triggered by renderthread::RenderThread - void onContextCreated(); void onContextDestroyed(); std::set<IGpuContextCallback*> mContextCallbacks; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index eaed46c44e5d..d177855e5a7d 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -20,10 +20,12 @@ #include "Layer.h" #include "Properties.h" #include "RenderThread.h" +#include "pipeline/skia/ATraceMemoryDump.h" #include "pipeline/skia/ShaderCache.h" #include "pipeline/skia/SkiaMemoryTracer.h" #include "renderstate/RenderState.h" #include "thread/CommonPool.h" +#include <utils/Trace.h> #include <GrContextOptions.h> #include <SkExecutor.h> @@ -184,6 +186,18 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) gpuTracer.logTotals(log); } +void CacheManager::onFrameCompleted() { + if (ATRACE_ENABLED()) { + static skiapipeline::ATraceMemoryDump tracer; + tracer.startFrame(); + SkGraphics::DumpMemoryStatistics(&tracer); + if (mGrContext) { + mGrContext->dumpMemoryStatistics(&tracer); + } + tracer.logTraces(); + } +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 968251e9f467..b009cc4f48f2 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -50,6 +50,7 @@ public: size_t getCacheSize() const { return mMaxResourceBytes; } size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; } + void onFrameCompleted(); private: friend class RenderThread; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 84902210a751..5993e176f0b8 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -16,7 +16,6 @@ #include "CanvasContext.h" -#include <GpuMemoryTracker.h> #include <apex/window.h> #include <fcntl.h> #include <strings.h> @@ -558,7 +557,7 @@ void CanvasContext::draw() { mJankTracker.finishGpuDraw(*forthBehind); } - GpuMemoryTracker::onFrameCompleted(); + mRenderThread.cacheManager().onFrameCompleted(); } // Called by choreographer to do an RT-driven animation diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index d78f641d45b9..cae3e3b5188c 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -270,7 +270,6 @@ void RenderThread::setGrContext(sk_sp<GrContext> context) { } mGrContext = std::move(context); if (mGrContext) { - mRenderState->onContextCreated(); DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize()); } } diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index 12c5b836f711..c4186174b637 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -16,24 +16,28 @@ #include "GraphicsStatsService.h" -#include "JankTracker.h" -#include "protos/graphicsstats.pb.h" - -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> -#include <log/log.h> - #include <errno.h> #include <fcntl.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <inttypes.h> +#include <log/log.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <algorithm> +#include <map> +#include <vector> + +#include "JankTracker.h" +#include "protos/graphicsstats.pb.h" + namespace android { namespace uirenderer { using namespace google::protobuf; +using namespace uirenderer::protos; constexpr int32_t sCurrentFileVersion = 1; constexpr int32_t sHeaderSize = 4; @@ -42,9 +46,9 @@ static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong" constexpr int sHistogramSize = ProfileData::HistogramSize(); constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize(); -static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, - const std::string& package, int64_t versionCode, - int64_t startTime, int64_t endTime, const ProfileData* data); +static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, + int64_t versionCode, int64_t startTime, int64_t endTime, + const ProfileData* data); static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd); class FileDescriptor { @@ -57,7 +61,7 @@ public: } } bool valid() { return mFd != -1; } - operator int() { return mFd; } // NOLINT(google-explicit-constructor) + operator int() { return mFd; } // NOLINT(google-explicit-constructor) private: int mFd; @@ -167,6 +171,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str } proto->set_package_name(package); proto->set_version_code(versionCode); + proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ? + GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN); auto summary = proto->mutable_summary(); summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); @@ -179,8 +185,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync)); summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); - summary->set_missed_deadline_count(summary->missed_deadline_count() - + data->jankTypeCount(kMissedDeadline)); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + data->jankTypeCount(kMissedDeadline)); bool creatingHistogram = false; if (proto->histogram_size() == 0) { @@ -365,17 +371,69 @@ void GraphicsStatsService::saveBuffer(const std::string& path, const std::string class GraphicsStatsService::Dump { public: - Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} + Dump(int outFd, DumpType type) : mFd(outFd), mType(type) { + if (mFd == -1 && mType == DumpType::Protobuf) { + mType = DumpType::ProtobufStatsd; + } + } int fd() { return mFd; } DumpType type() { return mType; } protos::GraphicsStatsServiceDumpProto& proto() { return mProto; } + void mergeStat(const protos::GraphicsStatsProto& stat); + void updateProto(); private: + // use package name and app version for a key + typedef std::pair<std::string, int64_t> DumpKey; + + std::map<DumpKey, protos::GraphicsStatsProto> mStats; int mFd; DumpType mType; protos::GraphicsStatsServiceDumpProto mProto; }; +void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) { + auto dumpKey = std::make_pair(stat.package_name(), stat.version_code()); + auto findIt = mStats.find(dumpKey); + if (findIt == mStats.end()) { + mStats[dumpKey] = stat; + } else { + auto summary = findIt->second.mutable_summary(); + summary->set_total_frames(summary->total_frames() + stat.summary().total_frames()); + summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames()); + summary->set_missed_vsync_count(summary->missed_vsync_count() + + stat.summary().missed_vsync_count()); + summary->set_high_input_latency_count(summary->high_input_latency_count() + + stat.summary().high_input_latency_count()); + summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() + + stat.summary().slow_ui_thread_count()); + summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + + stat.summary().slow_bitmap_upload_count()); + summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count()); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + stat.summary().missed_deadline_count()); + for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) { + auto bucket = findIt->second.mutable_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.histogram(bucketIndex).frame_count()); + } + for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size(); + bucketIndex++) { + auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.gpu_histogram(bucketIndex).frame_count()); + } + findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start())); + findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end())); + } +} + +void GraphicsStatsService::Dump::updateProto() { + for (auto& stat : mStats) { + mProto.add_stats()->CopyFrom(stat.second); + } +} + GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { return new Dump(outFd, type); } @@ -396,8 +454,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, path.empty() ? "<empty>" : path.c_str(), data); return; } - - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -409,7 +468,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { if (!parseFromFile(path, &statsProto)) { return; } - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -424,5 +485,79 @@ void GraphicsStatsService::finishDump(Dump* dump) { delete dump; } +class MemOutputStreamLite : public io::ZeroCopyOutputStream { +public: + explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {} + virtual ~MemOutputStreamLite() {} + + virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); } + + virtual void BackUp(int count) override { mImpl.BackUp(count); } + + virtual int64 ByteCount() const override { return mImpl.ByteCount(); } + + bool Flush() { return mImpl.Flush(); } + + void copyData(const DumpMemoryFn& reader, void* param1, void* param2) { + int bufferOffset = 0; + int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize; + int totalDataLeft = totalSize; + for (auto& it : mCopyAdapter.mBuffers) { + int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full + reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2); + bufferOffset += bufferSize; + totalDataLeft -= bufferSize; + } + } + +private: + struct MemAdapter : public io::CopyingOutputStream { + // Data is stored in an array of buffers. + // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer. + std::vector<std::vector<unsigned char>> mBuffers; + int mBuffersSize = 0; // total bytes allocated in mBuffers + int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back() + unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back() + + explicit MemAdapter() {} + virtual ~MemAdapter() {} + + virtual bool Write(const void* buffer, int size) override { + while (size > 0) { + if (0 == mCurrentBufferUnusedSize) { + mCurrentBufferUnusedSize = + std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000); + mBuffers.emplace_back(); + mBuffers.back().resize(mCurrentBufferUnusedSize); + mCurrentBuffer = mBuffers.back().data(); + mBuffersSize += mCurrentBufferUnusedSize; + } + int dataMoved = std::min(mCurrentBufferUnusedSize, size); + memcpy(mCurrentBuffer, buffer, dataMoved); + mCurrentBufferUnusedSize -= dataMoved; + mCurrentBuffer += dataMoved; + buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved; + size -= dataMoved; + } + return true; + } + }; + + MemOutputStreamLite::MemAdapter mCopyAdapter; + io::CopyingOutputStreamAdaptor mImpl; +}; + +void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2) { + MemOutputStreamLite stream; + dump->updateProto(); + bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush(); + delete dump; + if (!success) { + return; + } + stream.copyData(reader, param1, param2); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h index 389f599f439f..4bed96330a52 100644 --- a/libs/hwui/service/GraphicsStatsService.h +++ b/libs/hwui/service/GraphicsStatsService.h @@ -27,6 +27,9 @@ namespace protos { class GraphicsStatsProto; } +typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize, + void* param1, void* param2); + /* * The exported entry points used by GraphicsStatsService.java in f/b/services/core * @@ -40,6 +43,7 @@ public: enum class DumpType { Text, Protobuf, + ProtobufStatsd, }; ANDROID_API static void saveBuffer(const std::string& path, const std::string& package, @@ -52,6 +56,8 @@ public: int64_t startTime, int64_t endTime, const ProfileData* data); ANDROID_API static void addToDump(Dump* dump, const std::string& path); ANDROID_API static void finishDump(Dump* dump); + ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2); // Visible for testing static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output); diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp index 0a54aca4970d..e075d806126b 100644 --- a/libs/hwui/tests/common/TestContext.cpp +++ b/libs/hwui/tests/common/TestContext.cpp @@ -25,16 +25,16 @@ namespace test { static const int IDENT_DISPLAYEVENT = 1; static android::DisplayInfo DUMMY_DISPLAY{ - 1080, // w - 1920, // h - 320.0, // xdpi - 320.0, // ydpi - 60.0, // fps - 2.0, // density - 0, // orientation - false, // secure? - 0, // appVsyncOffset - 0, // presentationDeadline + 1080, // w + 1920, // h + 320.0, // xdpi + 320.0, // ydpi + 60.0, // fps + 2.0, // density + ui::ROTATION_0, // orientation + false, // secure? + 0, // appVsyncOffset + 0, // presentationDeadline }; DisplayInfo getInternalDisplay() { diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp deleted file mode 100644 index dac888cd79ca..000000000000 --- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp +++ /dev/null @@ -1,65 +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. - */ - -#include <GpuMemoryTracker.h> -#include <gtest/gtest.h> - -#include "renderthread/EglManager.h" -#include "renderthread/RenderThread.h" -#include "tests/common/TestUtils.h" - -#include <utils/StrongPointer.h> - -using namespace android; -using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; - -class TestGPUObject : public GpuMemoryTracker { -public: - TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {} - - void changeSize(int newSize) { notifySizeChanged(newSize); } -}; - -// Other tests may have created a renderthread and EGL context. -// This will destroy the EGLContext on RenderThread if it exists so that the -// current thread can spoof being a GPU thread -static void destroyEglContext() { - if (TestUtils::isRenderThreadRunning()) { - TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); }); - } -} - -TEST(GpuMemoryTracker, sizeCheck) { - destroyEglContext(); - - GpuMemoryTracker::onGpuContextCreated(); - ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - { - TestGPUObject myObj; - ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - myObj.changeSize(500); - ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - myObj.changeSize(1000); - ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - myObj.changeSize(300); - ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - } - ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - GpuMemoryTracker::onGpuContextDestroyed(); -} diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 8293b5f36c04..cb132f5b1101 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.util.SparseIntArray; import java.lang.annotation.Retention; @@ -127,6 +128,23 @@ public final class AudioDeviceInfo { * A device type describing a Hearing Aid. */ public static final int TYPE_HEARING_AID = 23; + /** + * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built + * in a device, that is specifically tuned for outputting sounds like notifications and alarms + * (i.e. sounds the user couldn't necessarily anticipate). + * <p>Note that this physical audio device may be the same as {@link #TYPE_BUILTIN_SPEAKER} + * but is driven differently to safely accommodate the different use case.</p> + */ + public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; + /** + * @hide + * A device type for rerouting audio within the Android framework between mixes and + * system applications. Typically created when using + * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the + * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag. + */ + @SystemApi + public static final int TYPE_REMOTE_SUBMIX = 25; /** @hide */ @IntDef(flag = false, prefix = "TYPE", value = { @@ -228,6 +246,8 @@ public final class AudioDeviceInfo { case TYPE_IP: case TYPE_BUS: case TYPE_HEARING_AID: + case TYPE_BUILTIN_SPEAKER_SAFE: + case TYPE_REMOTE_SUBMIX: return true; default: return false; @@ -253,6 +273,7 @@ public final class AudioDeviceInfo { case TYPE_LINE_DIGITAL: case TYPE_IP: case TYPE_BUS: + case TYPE_REMOTE_SUBMIX: return true; default: return false; @@ -449,6 +470,9 @@ public final class AudioDeviceInfo { INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID); + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE, + TYPE_BUILTIN_SPEAKER_SAFE); + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO); @@ -468,10 +492,7 @@ public final class AudioDeviceInfo { INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS); - - // not covered here, legacy - //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX - //AudioSystem.DEVICE_IN_REMOTE_SUBMIX + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX); // privileges mapping to output device EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray(); @@ -498,6 +519,9 @@ public final class AudioDeviceInfo { EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID); + EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE, + AudioSystem.DEVICE_OUT_SPEAKER_SAFE); + EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX); } } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e410882718d1..1b870e8e94ef 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4331,6 +4331,26 @@ public class AudioManager { } } + /** + * @hide + * Get the audio devices that would be used for the routing of the given audio attributes. + * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @return an empty list if there was an issue with the request, a list of audio devices + * otherwise (typically one device, except for duplicated paths). + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull List<AudioDeviceAddress> getDevicesForAttributes( + @NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes); + final IAudioService service = getService(); + try { + return service.getDevicesForAttributes(attributes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Indicate wired accessory connection state change. * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx) diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index e584addb35fc..fe57e71cca8c 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -33,6 +33,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET @@ -1077,6 +1078,41 @@ public class AudioSystem @UnsupportedAppUsage public static native int getDevicesForStream(int stream); + /** + * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)} + * Get the audio devices that would be used for the routing of the given audio attributes. + * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @return an empty list if there was an issue with the request, a list of audio devices + * otherwise (typically one device, except for duplicated paths). + */ + public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes( + @NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes); + final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING]; + final int res = getDevicesForAttributes(attributes, devices); + final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>(); + if (res != SUCCESS) { + Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes); + return routeDevices; + } + + for (AudioDeviceAddress device : devices) { + if (device != null) { + routeDevices.add(device); + } + } + return routeDevices; + } + + /** + * Maximum number of audio devices a track is ever routed to, determines the size of the + * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])} + */ + private static final int MAX_DEVICE_ROUTING = 4; + + private static native int getDevicesForAttributes(@NonNull AudioAttributes aa, + @NonNull AudioDeviceAddress[] devices); + /** @hide returns true if master mono is enabled. */ public static native boolean getMasterMono(); /** @hide enables or disables the master mono mode. */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ad7335e5b683..8e8385d382fb 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -272,6 +272,8 @@ interface IAudioService { AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy); + List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes); + // WARNING: read warning at top of file, new methods that need to be used by native // code via IAudioManager.h need to be added to the top section. } diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 28bf84d6e079..5dd0b1c915bd 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -24,7 +24,8 @@ import android.media.IMediaRoute2ProviderClient; */ oneway interface IMediaRoute2Provider { void setClient(IMediaRoute2ProviderClient client); - void requestCreateSession(String packageName, String routeId, String routeType, long requestId); + void requestCreateSession(String packageName, String routeId, + String routeFeature, long requestId); void releaseSession(String sessionId); void selectRoute(String sessionId, String routeId); diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl index bcb2336dbf78..0fccb3a9aeac 100644 --- a/media/java/android/media/IMediaRoute2ProviderClient.aidl +++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl @@ -18,15 +18,17 @@ package android.media; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2Info; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.Bundle; /** * @hide */ oneway interface IMediaRoute2ProviderClient { - void updateState(in MediaRoute2ProviderInfo providerInfo, - in List<RouteSessionInfo> sessionInfos); - void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId); - void notifySessionInfoChanged(in RouteSessionInfo sessionInfo); + // TODO: Change it to updateRoutes? + void updateState(in MediaRoute2ProviderInfo providerInfo); + void notifySessionCreated(in RoutingSessionInfo sessionInfo, long requestId); + void notifySessionCreationFailed(long requestId); + void notifySessionUpdated(in RoutingSessionInfo sessionInfo); + void notifySessionReleased(in RoutingSessionInfo sessionInfo); } diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl index f90c7c59cbeb..bc7ebea2e61f 100644 --- a/media/java/android/media/IMediaRouter2Client.aidl +++ b/media/java/android/media/IMediaRouter2Client.aidl @@ -17,7 +17,7 @@ package android.media; import android.media.MediaRoute2Info; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.Bundle; /** @@ -28,7 +28,7 @@ oneway interface IMediaRouter2Client { void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); - void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId); - void notifySessionInfoChanged(in RouteSessionInfo sessionInfo); - void notifySessionReleased(in RouteSessionInfo sessionInfo); + void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, int requestId); + void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo); + void notifySessionReleased(in RoutingSessionInfo sessionInfo); } diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index e8af21e59cc1..e9add1756224 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -24,7 +24,7 @@ import android.media.MediaRoute2Info; */ oneway interface IMediaRouter2Manager { void notifyRouteSelected(String packageName, in MediaRoute2Info route); - void notifyRouteTypesChanged(String packageName, in List<String> routeTypes); + void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index b573f64da51d..3cdaa0794b31 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -22,8 +22,8 @@ import android.media.IMediaRouter2Manager; import android.media.IMediaRouterClient; import android.media.MediaRoute2Info; import android.media.MediaRouterClientState; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; /** * {@hide} @@ -52,8 +52,8 @@ interface IMediaRouterService { void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction); void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, - String routeType, int requestId); - void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryRequest request); + String routeFeature, int requestId); + void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference); void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); @@ -70,5 +70,5 @@ interface IMediaRouterService { void requestUpdateVolume2Manager(IMediaRouter2Manager manager, in MediaRoute2Info route, int direction); - List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager); + List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager); } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 1ed53d942b63..021e23e190b3 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -36,7 +36,6 @@ import java.util.Objects; /** * Describes the properties of a route. - * @hide */ public final class MediaRoute2Info implements Parcelable { @NonNull @@ -56,7 +55,7 @@ public final class MediaRoute2Info implements Parcelable { @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING, CONNECTION_STATE_CONNECTED}) @Retention(RetentionPolicy.SOURCE) - private @interface ConnectionState {} + public @interface ConnectionState {} /** * The default connection state indicating the route is disconnected. @@ -99,16 +98,15 @@ public final class MediaRoute2Info implements Parcelable { /** @hide */ @IntDef({ - DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, - DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH}) + DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_REMOTE_TV, + DEVICE_TYPE_REMOTE_SPEAKER, DEVICE_TYPE_BLUETOOTH}) @Retention(RetentionPolicy.SOURCE) - private @interface DeviceType {} + public @interface DeviceType {} /** * The default receiver device type of the route indicating the type is unknown. * * @see #getDeviceType - * @hide */ public static final int DEVICE_TYPE_UNKNOWN = 0; @@ -118,7 +116,7 @@ public final class MediaRoute2Info implements Parcelable { * * @see #getDeviceType */ - public static final int DEVICE_TYPE_TV = 1; + public static final int DEVICE_TYPE_REMOTE_TV = 1; /** * A receiver device type of the route indicating the presentation of the media is happening @@ -126,14 +124,13 @@ public final class MediaRoute2Info implements Parcelable { * * @see #getDeviceType */ - public static final int DEVICE_TYPE_SPEAKER = 2; + public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; /** * A receiver device type of the route indicating the presentation of the media is happening * on a bluetooth device such as a bluetooth speaker. * * @see #getDeviceType - * @hide */ public static final int DEVICE_TYPE_BLUETOOTH = 3; @@ -152,7 +149,7 @@ public final class MediaRoute2Info implements Parcelable { @Nullable final String mClientPackageName; @NonNull - final List<String> mRouteTypes; + final List<String> mFeatures; final int mVolume; final int mVolumeMax; final int mVolumeHandling; @@ -168,7 +165,7 @@ public final class MediaRoute2Info implements Parcelable { mConnectionState = builder.mConnectionState; mIconUri = builder.mIconUri; mClientPackageName = builder.mClientPackageName; - mRouteTypes = builder.mRouteTypes; + mFeatures = builder.mFeatures; mVolume = builder.mVolume; mVolumeMax = builder.mVolumeMax; mVolumeHandling = builder.mVolumeHandling; @@ -184,7 +181,7 @@ public final class MediaRoute2Info implements Parcelable { mConnectionState = in.readInt(); mIconUri = in.readParcelable(null); mClientPackageName = in.readString(); - mRouteTypes = in.createStringArrayList(); + mFeatures = in.createStringArrayList(); mVolume = in.readInt(); mVolumeMax = in.readInt(); mVolumeHandling = in.readInt(); @@ -223,7 +220,7 @@ public final class MediaRoute2Info implements Parcelable { && (mConnectionState == other.mConnectionState) && Objects.equals(mIconUri, other.mIconUri) && Objects.equals(mClientPackageName, other.mClientPackageName) - && Objects.equals(mRouteTypes, other.mRouteTypes) + && Objects.equals(mFeatures, other.mFeatures) && (mVolume == other.mVolume) && (mVolumeMax == other.mVolumeMax) && (mVolumeHandling == other.mVolumeHandling) @@ -235,7 +232,7 @@ public final class MediaRoute2Info implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri, - mRouteTypes, mVolume, mVolumeMax, mVolumeHandling, mDeviceType); + mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType); } /** @@ -245,7 +242,7 @@ public final class MediaRoute2Info implements Parcelable { * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method * can be different from what was set in {@link MediaRoute2ProviderService}. * - * @see Builder#setId(String) + * @see Builder#Builder(String, CharSequence) */ @NonNull public String getId() { @@ -257,7 +254,7 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Gets the original id set by {@link Builder#setId(String)}. + * Gets the original id set by {@link Builder#Builder(String, CharSequence)}. * @hide */ @NonNull @@ -324,16 +321,16 @@ public final class MediaRoute2Info implements Parcelable { * Gets the supported categories of the route. */ @NonNull - public List<String> getRouteTypes() { - return mRouteTypes; + public List<String> getFeatures() { + return mFeatures; } - //TODO: once device types are confirmed, reflect those into the comment. /** * Gets the type of the receiver device associated with this route. * * @return The type of the receiver device associated with this route: - * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}. + * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER}, + * {@link #DEVICE_TYPE_BLUETOOTH}. */ @DeviceType public int getDeviceType() { @@ -369,15 +366,15 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Returns if the route contains at least one of the specified route types. + * Returns if the route has at least one of the specified route features. * - * @param routeTypes the list of route types to consider - * @return true if the route contains at least one type in the list + * @param features the list of route features to consider + * @return true if the route has at least one feature in the list */ - public boolean containsRouteTypes(@NonNull Collection<String> routeTypes) { - Objects.requireNonNull(routeTypes, "routeTypes must not be null"); - for (String routeType : routeTypes) { - if (getRouteTypes().contains(routeType)) { + public boolean hasAnyFeatures(@NonNull Collection<String> features) { + Objects.requireNonNull(features, "features must not be null"); + for (String feature : features) { + if (getFeatures().contains(feature)) { return true; } } @@ -398,7 +395,7 @@ public final class MediaRoute2Info implements Parcelable { dest.writeInt(mConnectionState); dest.writeParcelable(mIconUri, flags); dest.writeString(mClientPackageName); - dest.writeStringList(mRouteTypes); + dest.writeStringList(mFeatures); dest.writeInt(mVolume); dest.writeInt(mVolumeMax); dest.writeInt(mVolumeHandling); @@ -428,15 +425,15 @@ public final class MediaRoute2Info implements Parcelable { * Builder for {@link MediaRoute2Info media route info}. */ public static final class Builder { - String mId; + final String mId; String mProviderId; - CharSequence mName; + final CharSequence mName; CharSequence mDescription; @ConnectionState int mConnectionState; Uri mIconUri; String mClientPackageName; - List<String> mRouteTypes; + List<String> mFeatures; int mVolume; int mVolumeMax; int mVolumeHandling = PLAYBACK_VOLUME_FIXED; @@ -444,27 +441,43 @@ public final class MediaRoute2Info implements Parcelable { int mDeviceType = DEVICE_TYPE_UNKNOWN; Bundle mExtras; + /** + * Constructor for builder to create {@link MediaRoute2Info}. + * <p> + * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info + * obtained from {@link MediaRouter2} can be different from what was set in + * {@link MediaRoute2ProviderService}. + * </p> + * @param id + * @param name + */ public Builder(@NonNull String id, @NonNull CharSequence name) { - setId(id); - setName(name); - mRouteTypes = new ArrayList<>(); + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must not be empty"); + } + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("name must not be empty"); + } + mId = id; + mName = name; + mFeatures = new ArrayList<>(); } public Builder(@NonNull MediaRoute2Info routeInfo) { if (routeInfo == null) { throw new IllegalArgumentException("route info must not be null"); } + mId = routeInfo.mId; + mName = routeInfo.mName; - setId(routeInfo.mId); if (!TextUtils.isEmpty(routeInfo.mProviderId)) { setProviderId(routeInfo.mProviderId); } - setName(routeInfo.mName); mDescription = routeInfo.mDescription; mConnectionState = routeInfo.mConnectionState; mIconUri = routeInfo.mIconUri; setClientPackageName(routeInfo.mClientPackageName); - setRouteTypes(routeInfo.mRouteTypes); + mFeatures = new ArrayList<>(routeInfo.mFeatures); setVolume(routeInfo.mVolume); setVolumeMax(routeInfo.mVolumeMax); setVolumeHandling(routeInfo.mVolumeHandling); @@ -475,26 +488,6 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the unique id of the route. The value given here must be unique for each of your - * route. - * <p> - * In order to ensure uniqueness in {@link MediaRouter2} side, the value of - * {@link MediaRoute2Info#getId()} can be different from what was set in - * {@link MediaRoute2ProviderService}. - * </p> - * - * @see MediaRoute2Info#getId() - */ - @NonNull - public Builder setId(@NonNull String id) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("id must not be null or empty"); - } - mId = id; - return this; - } - - /** * Sets the provider id of the route. * @hide */ @@ -508,20 +501,10 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the user-visible name of the route. - */ - @NonNull - public Builder setName(@NonNull CharSequence name) { - Objects.requireNonNull(name, "name must not be null"); - mName = name; - return this; - } - - /** * Sets the user-visible description of the route. */ @NonNull - public Builder setDescription(@Nullable String description) { + public Builder setDescription(@Nullable CharSequence description) { mDescription = description; return this; } @@ -569,35 +552,35 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the types of the route. + * Clears the features of the route. */ @NonNull - public Builder setRouteTypes(@NonNull Collection<String> routeTypes) { - mRouteTypes = new ArrayList<>(); - return addRouteTypes(routeTypes); + public Builder clearFeatures() { + mFeatures = new ArrayList<>(); + return this; } /** - * Adds types for the route. + * Adds features for the route. */ @NonNull - public Builder addRouteTypes(@NonNull Collection<String> routeTypes) { - Objects.requireNonNull(routeTypes, "routeTypes must not be null"); - for (String routeType: routeTypes) { - addRouteType(routeType); + public Builder addFeatures(@NonNull Collection<String> features) { + Objects.requireNonNull(features, "features must not be null"); + for (String feature : features) { + addFeature(feature); } return this; } /** - * Add a type for the route. + * Adds a feature for the route. */ @NonNull - public Builder addRouteType(@NonNull String routeType) { - if (TextUtils.isEmpty(routeType)) { - throw new IllegalArgumentException("routeType must not be null or empty"); + public Builder addFeature(@NonNull String feature) { + if (TextUtils.isEmpty(feature)) { + throw new IllegalArgumentException("feature must not be null or empty"); } - mRouteTypes.add(routeType); + mFeatures.add(feature); return this; } @@ -642,7 +625,7 @@ public final class MediaRoute2Info implements Parcelable { */ @NonNull public Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; + mExtras = new Bundle(extras); return this; } diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java index e2f246cae8c2..c9a2ec7ed095 100644 --- a/media/java/android/media/MediaRoute2ProviderInfo.java +++ b/media/java/android/media/MediaRoute2ProviderInfo.java @@ -93,6 +93,9 @@ public final class MediaRoute2ProviderInfo implements Parcelable { /** * Gets the route for the given route id or null if no matching route exists. + * Please note that id should be original id. + * + * @see MediaRoute2Info#getOriginalId() */ @Nullable public MediaRoute2Info getRoute(@NonNull String routeId) { @@ -168,7 +171,7 @@ public final class MediaRoute2ProviderInfo implements Parcelable { MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue()) .setProviderId(mUniqueId) .build(); - newRoutes.put(routeWithProviderId.getId(), routeWithProviderId); + newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId); } mRoutes.clear(); @@ -183,14 +186,14 @@ public final class MediaRoute2ProviderInfo implements Parcelable { public Builder addRoute(@NonNull MediaRoute2Info route) { Objects.requireNonNull(route, "route must not be null"); - if (mRoutes.containsValue(route)) { - throw new IllegalArgumentException("route descriptor already added"); + if (mRoutes.containsKey(route.getOriginalId())) { + throw new IllegalArgumentException("A route with the same id is already added"); } if (mUniqueId != null) { - mRoutes.put(route.getId(), + mRoutes.put(route.getOriginalId(), new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build()); } else { - mRoutes.put(route.getId(), route); + mRoutes.put(route.getOriginalId(), route); } return this; } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 24b65baebcbd..6d9aea512f01 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -36,18 +36,38 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** - * @hide + * Base class for media route provider services. + * <p> + * The system media router service will bind to media route provider services when a + * {@link RouteDiscoveryPreference discovery preference} is registered via + * a {@link MediaRouter2 media router} by an application. + * </p><p> + * To implement your own media route provider service, extend this class and + * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish + * {@link MediaRoute2Info routes}. + * </p> */ public abstract class MediaRoute2ProviderService extends Service { private static final String TAG = "MR2ProviderService"; public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; + /** + * The request ID to pass {@link #notifySessionCreated(RoutingSessionInfo, long)} + * when {@link MediaRoute2ProviderService} created a session although there was no creation + * request. + * + * @see #notifySessionCreated(RoutingSessionInfo, long) + * @hide + */ + public static final long REQUEST_ID_UNKNOWN = 0; + private final Handler mHandler; private final Object mSessionLock = new Object(); private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false); @@ -56,13 +76,14 @@ public abstract class MediaRoute2ProviderService extends Service { private MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mSessionLock") - private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); + private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>(); public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); } @Override + @NonNull public IBinder onBind(@NonNull Intent intent) { //TODO: Allow binding from media router service only? if (SERVICE_INTERFACE.equals(intent.getAction())) { @@ -79,6 +100,7 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param routeId the id of the target route * @param request the media control request intent + * @hide */ //TODO: Discuss what to use for request (e.g., Intent? Request class?) public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request); @@ -88,6 +110,7 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param routeId the id of the route * @param volume the target volume + * @hide */ public abstract void onSetVolume(@NonNull String routeId, int volume); @@ -96,6 +119,7 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param routeId id of the route * @param delta the delta to add to the current volume + * @hide */ public abstract void onUpdateVolume(@NonNull String routeId, int delta); @@ -104,10 +128,11 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param sessionId id of the session * @return information of the session with the given id. - * null if the session is destroyed or id is not valid. + * null if the session is released or ID is not valid. + * @hide */ @Nullable - public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) { + public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) { if (TextUtils.isEmpty(sessionId)) { throw new IllegalArgumentException("sessionId must not be empty"); } @@ -117,221 +142,246 @@ public abstract class MediaRoute2ProviderService extends Service { } /** - * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains. + * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains. + * @hide */ @NonNull - public final List<RouteSessionInfo> getAllSessionInfo() { + public final List<RoutingSessionInfo> getAllSessionInfo() { synchronized (mSessionLock) { return new ArrayList<>(mSessionInfo.values()); } } /** - * Updates the information of a session. - * If the session is destroyed or not created before, it will be ignored. - * A session will be destroyed if it has no selected route. - * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of - * session info changes. + * Notifies clients of that the session is created and ready for use. + * <p> + * If this session is created without any creation request, use {@link #REQUEST_ID_UNKNOWN} + * as the request ID. * - * @param sessionInfo new session information - * @see #notifySessionCreated(RouteSessionInfo, long) + * @param sessionInfo information of the new session. + * The {@link RoutingSessionInfo#getId() id} of the session must be unique. + * @param requestId id of the previous request to create this session provided in + * {@link #onCreateSession(String, String, String, long)} + * @see #onCreateSession(String, String, String, long) + * @hide */ - public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) { + public final void notifySessionCreated(@NonNull RoutingSessionInfo sessionInfo, + long requestId) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - String sessionId = sessionInfo.getId(); - if (sessionInfo.getSelectedRoutes().isEmpty()) { - releaseSession(sessionId); - return; - } + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { - mSessionInfo.put(sessionId, sessionInfo); - schedulePublishState(); - } else { - Log.w(TAG, "Ignoring unknown session info."); + Log.w(TAG, "Ignoring duplicate session id."); return; } + mSessionInfo.put(sessionInfo.getId(), sessionInfo); + } + schedulePublishState(); + + if (mClient == null) { + return; + } + try { + // TODO: Calling binder calls in multiple thread may cause timing issue. + // Consider to change implementations to avoid the problems. + // For example, post binder calls, always send all sessions at once, etc. + mClient.notifySessionCreated(sessionInfo, requestId); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session created."); } } /** - * Notifies the session is changed. + * Notifies clients of that the session could not be created. * - * TODO: This method is temporary, only created for tests. Remove when the alternative is ready. + * @param requestId id of the previous request to create the session provided in + * {@link #onCreateSession(String, String, String, long)}. + * @see #onCreateSession(String, String, String, long) * @hide */ - public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) { - Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - - String sessionId = sessionInfo.getId(); - synchronized (mSessionLock) { - if (mSessionInfo.containsKey(sessionId)) { - mSessionInfo.put(sessionId, sessionInfo); - } else { - Log.w(TAG, "Ignoring unknown session info."); - return; - } - } - + public final void notifySessionCreationFailed(long requestId) { if (mClient == null) { return; } try { - mClient.notifySessionInfoChanged(sessionInfo); + mClient.notifySessionCreationFailed(requestId); } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session info changed."); + Log.w(TAG, "Failed to notify session creation failed."); } } /** - * Notifies clients of that the session is created and ready for use. If the session can be - * controlled, pass a {@link Bundle} that contains how to control it. + * Notifies the existing session is updated. For example, when + * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed. * - * @param sessionInfo information of the new session. - * The {@link RouteSessionInfo#getId() id} of the session must be - * unique. Pass {@code null} to reject the request or inform clients that - * session creation is failed. - * @param requestId id of the previous request to create this session + * @hide */ - // TODO: fail reason? - // TODO: Maybe better to create notifySessionCreationFailed? - public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { - if (sessionInfo != null) { - String sessionId = sessionInfo.getId(); - synchronized (mSessionLock) { - if (mSessionInfo.containsKey(sessionId)) { - Log.w(TAG, "Ignoring duplicate session id."); - return; - } - mSessionInfo.put(sessionInfo.getId(), sessionInfo); + public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + String sessionId = sessionInfo.getId(); + synchronized (mSessionLock) { + if (mSessionInfo.containsKey(sessionId)) { + mSessionInfo.put(sessionId, sessionInfo); + } else { + Log.w(TAG, "Ignoring unknown session info."); + return; } - schedulePublishState(); } if (mClient == null) { return; } try { - mClient.notifySessionCreated(sessionInfo, requestId); + mClient.notifySessionUpdated(sessionInfo); } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session created."); + Log.w(TAG, "Failed to notify session info changed."); } } /** - * Releases a session with the given id. - * {@link #onDestroySession} is called if the session is released. + * Notifies that the session is released. * - * @param sessionId id of the session to be released - * @see #onDestroySession(String, RouteSessionInfo) + * @param sessionId id of the released session. + * @see #onReleaseSession(String) + * @hide */ - public final void releaseSession(@NonNull String sessionId) { + public final void notifySessionReleased(@NonNull String sessionId) { if (TextUtils.isEmpty(sessionId)) { throw new IllegalArgumentException("sessionId must not be empty"); } - //TODO: notify media router service of release. - RouteSessionInfo sessionInfo; + RoutingSessionInfo sessionInfo; synchronized (mSessionLock) { sessionInfo = mSessionInfo.remove(sessionId); } - if (sessionInfo != null) { - mHandler.sendMessage(obtainMessage( - MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo)); - schedulePublishState(); + + if (sessionInfo == null) { + Log.w(TAG, "Ignoring unknown session info."); + return; + } + + if (mClient == null) { + return; + } + try { + mClient.notifySessionReleased(sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session info changed."); } } /** - * Called when a session should be created. + * Called when the service receives a request to create a session. + * <p> * You should create and maintain your own session and notifies the client of - * session info. Call {@link #notifySessionCreated(RouteSessionInfo, long)} + * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)} * with the given {@code requestId} to notify the information of a new session. - * If you can't create the session or want to reject the request, pass {@code null} - * as session info in {@link #notifySessionCreated(RouteSessionInfo, long)} - * with the given {@code requestId}. + * The created session must have the same route feature and must include the given route + * specified by {@code routeId}. + * <p> + * If the session can be controlled, you can optionally pass the control hints to + * {@link RoutingSessionInfo.Builder#setControlHints(Bundle)}. Control hints is a + * {@link Bundle} which contains how to control the session. + * <p> + * If you can't create the session or want to reject the request, call + * {@link #notifySessionCreationFailed(long)} with the given {@code requestId}. * * @param packageName the package name of the application that selected the route * @param routeId the id of the route initially being connected - * @param routeType the route type of the new session + * @param routeFeature the route feature of the new session * @param requestId the id of this session creation request + * + * @see RoutingSessionInfo.Builder#Builder(String, String, String) + * @see RoutingSessionInfo.Builder#addSelectedRoute(String) + * @see RoutingSessionInfo.Builder#setControlHints(Bundle) + * @hide */ public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId, - @NonNull String routeType, long requestId); + @NonNull String routeFeature, long requestId); /** - * Called when a session is about to be destroyed. - * You can clean up your session here. This can happen by the - * client or provider itself. + * Called when the session should be released. A client of the session or system can request + * a session to be released. + * <p> + * After releasing the session, call {@link #notifySessionReleased(String)} + * with the ID of the released session. * - * @param sessionId id of the session being destroyed. - * @param lastSessionInfo information of the session being destroyed. - * @see #releaseSession(String) + * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger + * this method to be called. + * + * @param sessionId id of the session being released. + * @see #notifySessionReleased(String) + * @see #getSessionInfo(String) + * @hide */ - public abstract void onDestroySession(@NonNull String sessionId, - @NonNull RouteSessionInfo lastSessionInfo); + public abstract void onReleaseSession(@NonNull String sessionId); //TODO: make a way to reject the request /** * Called when a client requests selecting a route for the session. - * After the route is selected, call {@link #updateSessionInfo(RouteSessionInfo)} to update - * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify - * clients of updated session info. + * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)} + * to update session info. * * @param sessionId id of the session * @param routeId id of the route - * @see #updateSessionInfo(RouteSessionInfo) + * @hide */ public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** * Called when a client requests deselecting a route from the session. - * After the route is deselected, call {@link #updateSessionInfo(RouteSessionInfo)} to update - * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify - * clients of updated session info. + * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)} + * to update session info. * * @param sessionId id of the session * @param routeId id of the route + * @hide */ public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** * Called when a client requests transferring a session to a route. - * After the transfer is finished, call {@link #updateSessionInfo(RouteSessionInfo)} to update - * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify - * clients of updated session info. + * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)} + * to update session info. * * @param sessionId id of the session * @param routeId id of the route + * @hide */ public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId); /** - * Called when the {@link RouteDiscoveryRequest discovery request} has changed. + * Called when the {@link RouteDiscoveryPreference discovery preference} has changed. * <p> * Whenever an application registers a {@link MediaRouter2.RouteCallback callback}, - * it also provides a discovery request to specify types of routes that it is interested in. - * The media router combines all of these discovery request into a single discovery request - * and notifies each provider. + * it also provides a discovery preference to specify features of routes that it is interested + * in. The media router combines all of these discovery request into a single discovery + * preference and notifies each provider. * </p><p> - * The provider should examine {@link RouteDiscoveryRequest#getRouteTypes() route types} - * in the discovery request to determine what kind of routes it should try to discover - * and whether it should perform active or passive scans. In many cases, the provider may be - * able to save power by not performing any scans when the request doesn't have any matching - * route types. + * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures() + * preferred features} in the discovery preference to determine what kind of routes it should + * try to discover and whether it should perform active or passive scans. In many cases, + * the provider may be able to save power by not performing any scans when the request doesn't + * have any matching route features. * </p> * - * @param request the new discovery request + * @param preference the new discovery preference + * + * TODO: This method needs tests. */ - public void onDiscoveryRequestChanged(@NonNull RouteDiscoveryRequest request) {} + public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {} /** - * Updates provider info and publishes routes and session info. + * Updates routes of the provider and notifies the system media router service. */ - public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) { - mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null"); + public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) { + Objects.requireNonNull(routes, "routes must not be null"); + mProviderInfo = new MediaRoute2ProviderInfo.Builder() + .addRoutes(routes) + .build(); schedulePublishState(); } @@ -355,12 +405,12 @@ public abstract class MediaRoute2ProviderService extends Service { return; } - List<RouteSessionInfo> sessionInfos; + List<RoutingSessionInfo> sessionInfos; synchronized (mSessionLock) { sessionInfos = new ArrayList<>(mSessionInfo.values()); } try { - mClient.updateState(mProviderInfo, sessionInfos); + mClient.updateState(mProviderInfo); } catch (RemoteException ex) { Log.w(TAG, "Failed to send onProviderInfoUpdated"); } @@ -384,14 +434,15 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void requestCreateSession(String packageName, String routeId, - String routeType, long requestId) { + String routeFeature, long requestId) { if (!checkCallerisSystem()) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, - MediaRoute2ProviderService.this, packageName, routeId, routeType, + MediaRoute2ProviderService.this, packageName, routeId, routeFeature, requestId)); } + @Override public void releaseSession(@NonNull String sessionId) { if (!checkCallerisSystem()) { @@ -401,7 +452,7 @@ public abstract class MediaRoute2ProviderService extends Service { Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service."); return; } - mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession, + mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, MediaRoute2ProviderService.this, sessionId)); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 8ebf6174cabf..6d37c2df63ec 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -18,10 +18,7 @@ package android.media; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import static java.lang.annotation.RetentionPolicy.SOURCE; - import android.annotation.CallbackExecutor; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -37,7 +34,6 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; -import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -50,50 +46,13 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** - * A new Media Router - * @hide + * Media Router 2 allows applications to control the routing of media channels + * and streams from the current device to remote speakers and devices. * * TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler) * Not only MediaRouter2, but also to service / manager / provider. */ public class MediaRouter2 { - - /** @hide */ - @Retention(SOURCE) - @IntDef(value = { - SELECT_REASON_UNKNOWN, - SELECT_REASON_USER_SELECTED, - SELECT_REASON_FALLBACK, - SELECT_REASON_SYSTEM_SELECTED}) - public @interface SelectReason {} - - /** - * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the reason - * the route was selected is unknown. - */ - public static final int SELECT_REASON_UNKNOWN = 0; - - /** - * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route - * is selected in response to a user's request. For example, when a user has selected - * a different device to play media to. - */ - public static final int SELECT_REASON_USER_SELECTED = 1; - - /** - * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route - * is selected as a fallback route. For example, when Wi-Fi is disconnected, the device speaker - * may be selected as a fallback route. - */ - public static final int SELECT_REASON_FALLBACK = 2; - - /** - * This is passed from {@link com.android.server.media.MediaRouterService} when the route - * is selected in response to a request from other apps (e.g. System UI). - * @hide - */ - public static final int SELECT_REASON_SYSTEM_SELECTED = 3; - private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sRouterLock = new Object(); @@ -118,14 +77,14 @@ public class MediaRouter2 { final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @GuardedBy("sLock") - private RouteDiscoveryRequest mDiscoveryRequest = RouteDiscoveryRequest.EMPTY; + private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY; // TODO: Make MediaRouter2 is always connected to the MediaRouterService. @GuardedBy("sLock") Client2 mClient; @GuardedBy("sLock") - private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>(); + private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>(); private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1); @@ -137,6 +96,7 @@ public class MediaRouter2 { /** * Gets an instance of the media router associated with the context. */ + @NonNull public static MediaRouter2 getInstance(@NonNull Context context) { Objects.requireNonNull(context, "context must not be null"); synchronized (sRouterLock) { @@ -193,12 +153,12 @@ public class MediaRouter2 { */ public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor, @NonNull RouteCallback routeCallback, - @NonNull RouteDiscoveryRequest request) { + @NonNull RouteDiscoveryPreference preference) { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(routeCallback, "callback must not be null"); - Objects.requireNonNull(request, "request must not be null"); + Objects.requireNonNull(preference, "preference must not be null"); - RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, request); + RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference); if (!mRouteCallbackRecords.addIfAbsent(record)) { Log.w(TAG, "Ignoring the same callback"); return; @@ -210,15 +170,13 @@ public class MediaRouter2 { try { mMediaRouterService.registerClient2(client, mPackageName); updateDiscoveryRequestLocked(); - mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryRequest); + mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference); mClient = client; } catch (RemoteException ex) { Log.e(TAG, "Unable to register media router.", ex); } } } - - //TODO: Update discovery request here. } /** @@ -251,8 +209,8 @@ public class MediaRouter2 { } private void updateDiscoveryRequestLocked() { - mDiscoveryRequest = new RouteDiscoveryRequest.Builder( - mRouteCallbackRecords.stream().map(record -> record.mRequest).collect( + mDiscoveryPreference = new RouteDiscoveryPreference.Builder( + mRouteCallbackRecords.stream().map(record -> record.mPreference).collect( Collectors.toList())).build(); } @@ -261,8 +219,8 @@ public class MediaRouter2 { * known to the media router. * Please note that the list can be changed before callbacks are invoked. * - * @return the list of routes that contains at least one of the route types in discovery - * requests registered by the application + * @return the list of routes that contains at least one of the route features in discovery + * preferences registered by the application */ @NonNull public List<MediaRoute2Info> getRoutes() { @@ -272,7 +230,7 @@ public class MediaRouter2 { List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); for (MediaRoute2Info route : mRoutes.values()) { - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { filteredRoutes.add(route); } } @@ -283,12 +241,13 @@ public class MediaRouter2 { } /** - * Registers a callback to get updates on creations and changes of route sessions. + * Registers a callback to get updates on creations and changes of routing sessions. * If you register the same callback twice or more, it will be ignored. * * @param executor the executor to execute the callback on * @param callback the callback to register * @see #unregisterSessionCallback + * @hide */ @NonNull public void registerSessionCallback(@CallbackExecutor Executor executor, @@ -309,6 +268,7 @@ public class MediaRouter2 { * * @param callback the callback to unregister * @see #registerSessionCallback + * @hide */ @NonNull public void unregisterSessionCallback(@NonNull SessionCallback callback) { @@ -324,25 +284,26 @@ public class MediaRouter2 { * Requests the media route provider service to create a session with the given route. * * @param route the route you want to create a session with. - * @param routeType the route type of the session. Should not be empty + * @param routeFeature the route feature of the session. Should not be empty. * * @see SessionCallback#onSessionCreated * @see SessionCallback#onSessionCreationFailed + * @hide */ @NonNull public void requestCreateSession(@NonNull MediaRoute2Info route, - @NonNull String routeType) { + @NonNull String routeFeature) { Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(routeType)) { - throw new IllegalArgumentException("routeType must not be empty"); + if (TextUtils.isEmpty(routeFeature)) { + throw new IllegalArgumentException("routeFeature must not be empty"); } // TODO: Check the given route exists - // TODO: Check the route supports the given routeType + // TODO: Check the route supports the given routeFeature final int requestId; requestId = mSessionCreationRequestCnt.getAndIncrement(); - SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType); + SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature); mSessionCreationRequests.add(request); Client2 client; @@ -351,7 +312,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.requestCreateSession(client, route, routeType, requestId); + mMediaRouterService.requestCreateSession(client, route, routeFeature, requestId); } catch (RemoteException ex) { Log.e(TAG, "Unable to request to create session.", ex); mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, @@ -365,6 +326,7 @@ public class MediaRouter2 { * * @param route the route that will receive the control request * @param request the media control request + * @hide */ //TODO: Discuss what to use for request (e.g., Intent? Request class?) //TODO: Provide a way to obtain the result @@ -392,6 +354,7 @@ public class MediaRouter2 { * </p> * * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}. + * @hide */ public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) { Objects.requireNonNull(route, "route must not be null"); @@ -416,6 +379,7 @@ public class MediaRouter2 { * </p> * * @param delta The delta to add to the current volume. + * @hide */ public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) { Objects.requireNonNull(route, "route must not be null"); @@ -442,7 +406,7 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { addedRoutes.add(route); } } @@ -458,7 +422,7 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.remove(route.getId()); - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { removedRoutes.add(route); } } @@ -474,7 +438,7 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { changedRoutes.add(route); } } @@ -491,7 +455,7 @@ public class MediaRouter2 { * <p> * Pass {@code null} to sessionInfo for the failure case. */ - void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) { + void createControllerOnHandler(@Nullable RoutingSessionInfo sessionInfo, int requestId) { SessionCreationRequest matchingRequest = null; for (SessionCreationRequest request : mSessionCreationRequests) { if (request.mRequestId == requestId) { @@ -504,27 +468,27 @@ public class MediaRouter2 { mSessionCreationRequests.remove(matchingRequest); MediaRoute2Info requestedRoute = matchingRequest.mRoute; - String requestedRouteType = matchingRequest.mRouteType; + String requestedRouteFeature = matchingRequest.mRouteFeature; if (sessionInfo == null) { // TODO: We may need to distinguish between failure and rejection. // One way can be introducing 'reason'. - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; - } else if (!TextUtils.equals(requestedRouteType, - sessionInfo.getRouteType())) { - Log.w(TAG, "The session has different route type from what we requested. " - + "(requested=" + requestedRouteType - + ", actual=" + sessionInfo.getRouteType() + } else if (!TextUtils.equals(requestedRouteFeature, + sessionInfo.getRouteFeature())) { + Log.w(TAG, "The session has different route feature from what we requested. " + + "(requested=" + requestedRouteFeature + + ", actual=" + sessionInfo.getRouteFeature() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) { Log.w(TAG, "The session does not contain the requested route. " + "(requestedRouteId=" + requestedRoute.getId() + ", actualRoutes=" + sessionInfo.getSelectedRoutes() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; } else if (!TextUtils.equals(requestedRoute.getProviderId(), sessionInfo.getProviderId())) { @@ -532,29 +496,29 @@ public class MediaRouter2 { + "(requested route's providerId=" + requestedRoute.getProviderId() + ", actual providerId=" + sessionInfo.getProviderId() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; } } if (sessionInfo != null) { - RouteSessionController controller = new RouteSessionController(sessionInfo); + RoutingController controller = new RoutingController(sessionInfo); synchronized (sRouterLock) { - mSessionControllers.put(controller.getSessionId(), controller); + mRoutingControllers.put(controller.getSessionId(), controller); } notifySessionCreated(controller); } } - void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) { + void changeSessionInfoOnHandler(RoutingSessionInfo sessionInfo) { if (sessionInfo == null) { Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo."); return; } - RouteSessionController matchingController; + RoutingController matchingController; synchronized (sRouterLock) { - matchingController = mSessionControllers.get(sessionInfo.getId()); + matchingController = mRoutingControllers.get(sessionInfo.getId()); } if (matchingController == null) { @@ -563,27 +527,27 @@ public class MediaRouter2 { return; } - RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo(); + RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old=" + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId()); return; } - matchingController.setRouteSessionInfo(sessionInfo); + matchingController.setRoutingSessionInfo(sessionInfo); notifySessionInfoChanged(matchingController, oldInfo, sessionInfo); } - void releaseControllerOnHandler(RouteSessionInfo sessionInfo) { + void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) { if (sessionInfo == null) { Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo."); return; } final String uniqueSessionId = sessionInfo.getId(); - RouteSessionController matchingController; + RoutingController matchingController; synchronized (sRouterLock) { - matchingController = mSessionControllers.get(uniqueSessionId); + matchingController = mRoutingControllers.get(uniqueSessionId); } if (matchingController == null) { @@ -594,7 +558,7 @@ public class MediaRouter2 { return; } - RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo(); + RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { Log.w(TAG, "releaseControllerOnHandler: Provider IDs are not matched. old=" + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId()); @@ -602,23 +566,23 @@ public class MediaRouter2 { } synchronized (sRouterLock) { - mSessionControllers.remove(uniqueSessionId, matchingController); + mRoutingControllers.remove(uniqueSessionId, matchingController); } matchingController.release(); notifyControllerReleased(matchingController); } private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, - RouteDiscoveryRequest discoveryRequest) { + RouteDiscoveryPreference discoveryRequest) { return routes.stream() .filter( - route -> route.containsRouteTypes(discoveryRequest.getRouteTypes())) + route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures())) .collect(Collectors.toList()); } private void notifyRoutesAdded(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference); if (!filteredRoutes.isEmpty()) { record.mExecutor.execute( () -> record.mRouteCallback.onRoutesAdded(filteredRoutes)); @@ -628,7 +592,7 @@ public class MediaRouter2 { private void notifyRoutesRemoved(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference); if (!filteredRoutes.isEmpty()) { record.mExecutor.execute( () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes)); @@ -638,7 +602,7 @@ public class MediaRouter2 { private void notifyRoutesChanged(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference); if (!filteredRoutes.isEmpty()) { record.mExecutor.execute( () -> record.mRouteCallback.onRoutesChanged(filteredRoutes)); @@ -646,22 +610,22 @@ public class MediaRouter2 { } } - private void notifySessionCreated(RouteSessionController controller) { + private void notifySessionCreated(RoutingController controller) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( () -> record.mSessionCallback.onSessionCreated(controller)); } } - private void notifySessionCreationFailed(MediaRoute2Info route, String routeType) { + private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( - () -> record.mSessionCallback.onSessionCreationFailed(route, routeType)); + () -> record.mSessionCallback.onSessionCreationFailed(route, routeFeature)); } } - private void notifySessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + private void notifySessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( () -> record.mSessionCallback.onSessionInfoChanged( @@ -669,7 +633,7 @@ public class MediaRouter2 { } } - private void notifyControllerReleased(RouteSessionController controller) { + private void notifyControllerReleased(RoutingController controller) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( () -> record.mSessionCallback.onSessionReleased(controller)); @@ -710,23 +674,24 @@ public class MediaRouter2 { /** * Callback for receiving a result of session creation and session updates. + * @hide */ public static class SessionCallback { /** - * Called when the route session is created by the route provider. + * Called when the routing session is created by the route provider. * * @param controller the controller to control the created session */ - public void onSessionCreated(@NonNull RouteSessionController controller) {} + public void onSessionCreated(@NonNull RoutingController controller) {} /** * Called when the session creation request failed. * * @param requestedRoute the route info which was used for the request - * @param requestedRouteType the route type which was used for the request + * @param requestedRouteFeature the route feature which was used for the request */ public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute, - @NonNull String requestedRouteType) {} + @NonNull String requestedRouteFeature) {} /** * Called when the session info has changed. @@ -737,44 +702,45 @@ public class MediaRouter2 { * TODO: (Discussion) Do we really need newInfo? The controller has the newInfo. * However. there can be timing issue if there is no newInfo. */ - public void onSessionInfoChanged(@NonNull RouteSessionController controller, - @NonNull RouteSessionInfo oldInfo, - @NonNull RouteSessionInfo newInfo) {} + public void onSessionInfoChanged(@NonNull RoutingController controller, + @NonNull RoutingSessionInfo oldInfo, + @NonNull RoutingSessionInfo newInfo) {} /** * Called when the session is released by {@link MediaRoute2ProviderService}. * Before this method is called, the controller would be released by the system, - * which means the {@link RouteSessionController#isReleased()} will always return true + * which means the {@link RoutingController#isReleased()} will always return true * for the {@code controller} here. * <p> - * Note: Calling {@link RouteSessionController#release()} will <em>NOT</em> trigger + * Note: Calling {@link RoutingController#release()} will <em>NOT</em> trigger * this method to be called. * * TODO: Add tests for checking whether this method is called. * TODO: When service process dies, this should be called. * - * @see RouteSessionController#isReleased() + * @see RoutingController#isReleased() */ - public void onSessionReleased(@NonNull RouteSessionController controller) {} + public void onSessionReleased(@NonNull RoutingController controller) {} } /** - * A class to control media route session in media route provider. + * A class to control media routing session in media route provider. * For example, selecting/deselcting/transferring routes to session can be done through this * class. Instances are created by {@link MediaRouter2}. * * TODO: Need to add toString() + * @hide */ - public final class RouteSessionController { + public final class RoutingController { private final Object mControllerLock = new Object(); @GuardedBy("mLock") - private RouteSessionInfo mSessionInfo; + private RoutingSessionInfo mSessionInfo; @GuardedBy("mLock") private volatile boolean mIsReleased; - RouteSessionController(@NonNull RouteSessionInfo sessionInfo) { + RoutingController(@NonNull RoutingSessionInfo sessionInfo) { mSessionInfo = sessionInfo; } @@ -788,17 +754,17 @@ public class MediaRouter2 { } /** - * @return the type of routes that the session includes. + * @return the feature which is used by the session mainly. */ @NonNull - public String getRouteType() { + public String getRouteFeature() { synchronized (mControllerLock) { - return mSessionInfo.getRouteType(); + return mSessionInfo.getRouteFeature(); } } /** - * @return the control hints used to control route session if available. + * @return the control hints used to control routing session if available. */ @Nullable public Bundle getControlHints() { @@ -1020,7 +986,7 @@ public class MediaRouter2 { Client2 client; synchronized (sRouterLock) { - mSessionControllers.remove(getSessionId(), this); + mRoutingControllers.remove(getSessionId(), this); client = mClient; } if (client != null) { @@ -1037,13 +1003,13 @@ public class MediaRouter2 { * @hide */ @NonNull - public RouteSessionInfo getRouteSessionInfo() { + public RoutingSessionInfo getRoutingSessionInfo() { synchronized (mControllerLock) { return mSessionInfo; } } - void setRouteSessionInfo(@NonNull RouteSessionInfo info) { + void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) { synchronized (mControllerLock) { mSessionInfo = info; } @@ -1070,13 +1036,13 @@ public class MediaRouter2 { final class RouteCallbackRecord { public final Executor mExecutor; public final RouteCallback mRouteCallback; - public final RouteDiscoveryRequest mRequest; + public final RouteDiscoveryPreference mPreference; RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback, - @Nullable RouteDiscoveryRequest request) { + @Nullable RouteDiscoveryPreference preference) { mRouteCallback = routeCallback; mExecutor = executor; - mRequest = request; + mPreference = preference; } @Override @@ -1125,13 +1091,13 @@ public class MediaRouter2 { final class SessionCreationRequest { public final MediaRoute2Info mRoute; - public final String mRouteType; + public final String mRouteFeature; public final int mRequestId; SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, - @NonNull String routeType) { + @NonNull String routeFeature) { mRoute = route; - mRouteType = routeType; + mRouteFeature = routeFeature; mRequestId = requestId; } } @@ -1159,19 +1125,19 @@ public class MediaRouter2 { } @Override - public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) { + public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, int requestId) { mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, MediaRouter2.this, sessionInfo, requestId)); } @Override - public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) { + public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) { mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler, MediaRouter2.this, sessionInfo)); } @Override - public void notifySessionReleased(RouteSessionInfo sessionInfo) { + public void notifySessionReleased(RoutingSessionInfo sessionInfo) { mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler, MediaRouter2.this, sessionInfo)); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 1e6ec51442d6..70229335905b 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -65,7 +65,7 @@ public class MediaRouter2Manager { @GuardedBy("mRoutesLock") private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @NonNull - final ConcurrentMap<String, List<String>> mRouteTypeMap = new ConcurrentHashMap<>(); + final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>(); private AtomicInteger mNextRequestId = new AtomicInteger(1); @@ -144,7 +144,7 @@ public class MediaRouter2Manager { } //TODO: clear mRoutes? mClient = null; - mRouteTypeMap.clear(); + mPreferredFeaturesMap.clear(); } } } @@ -160,14 +160,14 @@ public class MediaRouter2Manager { public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); - List<String> routeTypes = mRouteTypeMap.get(packageName); - if (routeTypes == null) { + List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); + if (preferredFeatures == null) { return Collections.emptyList(); } List<MediaRoute2Info> routes = new ArrayList<>(); synchronized (mRoutesLock) { for (MediaRoute2Info route : mRoutes.values()) { - if (route.containsRouteTypes(routeTypes)) { + if (route.hasAnyFeatures(preferredFeatures)) { routes.add(route); } } @@ -176,7 +176,7 @@ public class MediaRouter2Manager { } @NonNull - public List<RouteSessionInfo> getActiveSessions() { + public List<RoutingSessionInfo> getActiveSessions() { Client client; synchronized (sLock) { client = mClient; @@ -352,15 +352,15 @@ public class MediaRouter2Manager { } } - void updateRouteTypes(String packageName, List<String> routeTypes) { - List<String> prevTypes = mRouteTypeMap.put(packageName, routeTypes); - if ((prevTypes == null && routeTypes.size() == 0) - || Objects.equals(routeTypes, prevTypes)) { + void updatePreferredFeatures(String packageName, List<String> preferredFeatures) { + List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures); + if ((prevFeatures == null && preferredFeatures.size() == 0) + || Objects.equals(preferredFeatures, prevFeatures)) { return; } for (CallbackRecord record : mCallbackRecords) { - record.mExecutor.execute( - () -> record.mCallback.onControlCategoriesChanged(packageName, routeTypes)); + record.mExecutor.execute(() -> record.mCallback + .onControlCategoriesChanged(packageName, preferredFeatures)); } } @@ -398,13 +398,13 @@ public class MediaRouter2Manager { /** - * Called when the route types of an app is changed. + * Called when the preferred route features of an app is changed. * * @param packageName the package name of the application - * @param routeTypes the list of route types set by an application. + * @param preferredFeatures the list of preferred route features set by an application. */ public void onControlCategoriesChanged(@NonNull String packageName, - @NonNull List<String> routeTypes) {} + @NonNull List<String> preferredFeatures) {} } final class CallbackRecord { @@ -440,9 +440,9 @@ public class MediaRouter2Manager { MediaRouter2Manager.this, packageName, route)); } - public void notifyRouteTypesChanged(String packageName, List<String> routeTypes) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateRouteTypes, - MediaRouter2Manager.this, packageName, routeTypes)); + public void notifyPreferredFeaturesChanged(String packageName, List<String> features) { + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures, + MediaRouter2Manager.this, packageName, features)); } @Override diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java new file mode 100644 index 000000000000..2c431b98fc7a --- /dev/null +++ b/media/java/android/media/MediaTranscodeManager.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; +import java.util.concurrent.locks.ReentrantLock; + +/** + * MediaTranscodeManager provides an interface to the system's media transcode service. + * Transcode requests are put in a queue and processed in order. When a transcode operation is + * completed the caller is notified via its OnTranscodingFinishedListener. In the meantime the + * caller may use the returned TranscodingJob object to cancel or check the status of a specific + * transcode operation. + * The currently supported media types are video and still images. + * + * TODO(lnilsson): Add sample code when API is settled. + * + * @hide + */ +public final class MediaTranscodeManager { + private static final String TAG = "MediaTranscodeManager"; + + // Invalid ID passed from native means the request was never enqueued. + private static final long ID_INVALID = -1; + + // Events passed from native. + private static final int EVENT_JOB_STARTED = 1; + private static final int EVENT_JOB_PROGRESSED = 2; + private static final int EVENT_JOB_FINISHED = 3; + + @IntDef(prefix = { "EVENT_" }, value = { + EVENT_JOB_STARTED, + EVENT_JOB_PROGRESSED, + EVENT_JOB_FINISHED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Event {} + + private static MediaTranscodeManager sMediaTranscodeManager; + private final ConcurrentMap<Long, TranscodingJob> mPendingTranscodingJobs = + new ConcurrentHashMap<>(); + private final Context mContext; + + /** + * Listener that gets notified when a transcoding operation has finished. + * This listener gets notified regardless of how the operation finished. It is up to the + * listener implementation to check the result and take appropriate action. + */ + @FunctionalInterface + public interface OnTranscodingFinishedListener { + /** + * Called when the transcoding operation has finished. The receiver may use the + * TranscodingJob to check the result, i.e. whether the operation succeeded, was canceled or + * if an error occurred. + * @param transcodingJob The TranscodingJob instance for the finished transcoding operation. + */ + void onTranscodingFinished(@NonNull TranscodingJob transcodingJob); + } + + /** + * Class describing a transcode operation to be performed. The caller uses this class to + * configure a transcoding operation that can then be enqueued using MediaTranscodeManager. + */ + public static final class TranscodingRequest { + private Uri mSrcUri; + private Uri mDstUri; + private MediaFormat mDstFormat; + + private TranscodingRequest(Builder b) { + mSrcUri = b.mSrcUri; + mDstUri = b.mDstUri; + mDstFormat = b.mDstFormat; + } + + /** TranscodingRequest builder class. */ + public static class Builder { + private Uri mSrcUri; + private Uri mDstUri; + private MediaFormat mDstFormat; + + /** + * Specifies the source media file. + * @param uri Content uri for the source media file. + * @return The builder instance. + */ + public Builder setSourceUri(Uri uri) { + mSrcUri = uri; + return this; + } + + /** + * Specifies the destination media file. + * @param uri Content uri for the destination media file. + * @return The builder instance. + */ + public Builder setDestinationUri(Uri uri) { + mDstUri = uri; + return this; + } + + /** + * Specifies the media format of the transcoded media file. + * @param dstFormat MediaFormat containing the desired destination format. + * @return The builder instance. + */ + public Builder setDestinationFormat(MediaFormat dstFormat) { + mDstFormat = dstFormat; + return this; + } + + /** + * Builds a new TranscodingRequest with the configuration set on this builder. + * @return A new TranscodingRequest. + */ + public TranscodingRequest build() { + return new TranscodingRequest(this); + } + } + } + + /** + * Handle to an enqueued transcoding operation. An instance of this class represents a single + * enqueued transcoding operation. The caller can use that instance to query the status or + * progress, and to get the result once the operation has completed. + */ + public static final class TranscodingJob { + /** The job is enqueued but not yet running. */ + public static final int STATUS_PENDING = 1; + /** The job is currently running. */ + public static final int STATUS_RUNNING = 2; + /** The job is finished. */ + public static final int STATUS_FINISHED = 3; + + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_PENDING, + STATUS_RUNNING, + STATUS_FINISHED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Status {} + + /** The job does not have a result yet. */ + public static final int RESULT_NONE = 1; + /** The job completed successfully. */ + public static final int RESULT_SUCCESS = 2; + /** The job encountered an error while running. */ + public static final int RESULT_ERROR = 3; + /** The job was canceled by the caller. */ + public static final int RESULT_CANCELED = 4; + + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_NONE, + RESULT_SUCCESS, + RESULT_ERROR, + RESULT_CANCELED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Result {} + + /** Listener that gets notified when the progress changes. */ + @FunctionalInterface + public interface OnProgressChangedListener { + + /** + * Called when the progress changes. The progress is between 0 and 1, where 0 means + * that the job has not yet started and 1 means that it has finished. + * @param progress The new progress. + */ + void onProgressChanged(float progress); + } + + private final Executor mExecutor; + private final OnTranscodingFinishedListener mListener; + private final ReentrantLock mStatusChangeLock = new ReentrantLock(); + private Executor mProgressChangedExecutor; + private OnProgressChangedListener mProgressChangedListener; + private long mID; + private float mProgress = 0.0f; + private @Status int mStatus = STATUS_PENDING; + private @Result int mResult = RESULT_NONE; + + private TranscodingJob(long id, @NonNull @CallbackExecutor Executor executor, + @NonNull OnTranscodingFinishedListener listener) { + mID = id; + mExecutor = executor; + mListener = listener; + } + + /** + * Set a progress listener. + * @param listener The progress listener. + */ + public void setOnProgressChangedListener(@NonNull @CallbackExecutor Executor executor, + @Nullable OnProgressChangedListener listener) { + mProgressChangedExecutor = executor; + mProgressChangedListener = listener; + } + + /** + * Cancels the transcoding job and notify the listener. If the job happened to finish before + * being canceled this call is effectively a no-op and will not update the result in that + * case. + */ + public void cancel() { + setJobFinished(RESULT_CANCELED); + sMediaTranscodeManager.native_cancelTranscodingRequest(mID); + } + + /** + * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means + * that the job has not yet started and 1 means that it is finished. + * @return The progress. + */ + public float getProgress() { + return mProgress; + } + + /** + * Gets the status of the transcoding job. + * @return The status. + */ + public @Status int getStatus() { + return mStatus; + } + + /** + * Gets the result of the transcoding job. + * @return The result. + */ + public @Result int getResult() { + return mResult; + } + + private void setJobStarted() { + mStatus = STATUS_RUNNING; + } + + private void setJobProgress(float newProgress) { + mProgress = newProgress; + + // Notify listener. + OnProgressChangedListener onProgressChangedListener = mProgressChangedListener; + if (onProgressChangedListener != null) { + mProgressChangedExecutor.execute( + () -> onProgressChangedListener.onProgressChanged(mProgress)); + } + } + + private void setJobFinished(int result) { + boolean doNotifyListener = false; + + // Prevent conflicting simultaneous status updates from native (finished) and from the + // caller (cancel). + try { + mStatusChangeLock.lock(); + if (mStatus != STATUS_FINISHED) { + mStatus = STATUS_FINISHED; + mResult = result; + doNotifyListener = true; + } + } finally { + mStatusChangeLock.unlock(); + } + + if (doNotifyListener) { + mExecutor.execute(() -> mListener.onTranscodingFinished(this)); + } + } + + private void processJobEvent(@Event int event, int arg) { + switch (event) { + case EVENT_JOB_STARTED: + setJobStarted(); + break; + case EVENT_JOB_PROGRESSED: + setJobProgress((float) arg / 100); + break; + case EVENT_JOB_FINISHED: + setJobFinished(arg); + break; + default: + Log.e(TAG, "Unsupported event: " + event); + break; + } + } + } + + // Initializes the native library. + private static native void native_init(); + // Requests a new job ID from the native service. + private native long native_requestUniqueJobID(); + // Enqueues a transcoding request to the native service. + private native boolean native_enqueueTranscodingRequest( + long id, @NonNull TranscodingRequest transcodingRequest, @NonNull Context context); + // Cancels an enqueued transcoding request. + private native void native_cancelTranscodingRequest(long id); + + // Private constructor. + private MediaTranscodeManager(@NonNull Context context) { + mContext = context; + } + + // Events posted from the native service. + @SuppressWarnings("unused") + private void postEventFromNative(@Event int event, long id, int arg) { + Log.d(TAG, String.format("postEventFromNative. Event %d, ID %d, arg %d", event, id, arg)); + + TranscodingJob transcodingJob = mPendingTranscodingJobs.get(id); + + // Job IDs are added to the tracking set before the job is enqueued so it should never + // be null unless the service misbehaves. + if (transcodingJob == null) { + Log.e(TAG, "No matching transcode job found for id " + id); + return; + } + + transcodingJob.processJobEvent(event, arg); + } + + /** + * Gets the MediaTranscodeManager singleton instance. + * @param context The application context. + * @return the {@link MediaTranscodeManager} singleton instance. + */ + public static MediaTranscodeManager getInstance(@NonNull Context context) { + Preconditions.checkNotNull(context); + synchronized (MediaTranscodeManager.class) { + if (sMediaTranscodeManager == null) { + sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext()); + } + return sMediaTranscodeManager; + } + } + + /** + * Enqueues a TranscodingRequest for execution. + * @param transcodingRequest The TranscodingRequest to enqueue. + * @param listenerExecutor Executor on which the listener is notified. + * @param listener Listener to get notified when the transcoding job is finished. + * @return A TranscodingJob for this operation. + */ + public @Nullable TranscodingJob enqueueTranscodingRequest( + @NonNull TranscodingRequest transcodingRequest, + @NonNull @CallbackExecutor Executor listenerExecutor, + @NonNull OnTranscodingFinishedListener listener) { + Log.i(TAG, "enqueueTranscodingRequest called."); + Preconditions.checkNotNull(transcodingRequest); + Preconditions.checkNotNull(listenerExecutor); + Preconditions.checkNotNull(listener); + + // Reserve a job ID. + long jobID = native_requestUniqueJobID(); + if (jobID == ID_INVALID) { + return null; + } + + // Add the job to the tracking set. + TranscodingJob transcodingJob = new TranscodingJob(jobID, listenerExecutor, listener); + mPendingTranscodingJobs.put(jobID, transcodingJob); + + // Enqueue the request with the native service. + boolean enqueued = native_enqueueTranscodingRequest(jobID, transcodingRequest, mContext); + if (!enqueued) { + mPendingTranscodingJobs.remove(jobID); + return null; + } + + return transcodingJob; + } + + static { + System.loadLibrary("media_jni"); + native_init(); + } +} diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl index 744f6569325d..898eb39e7c56 100644 --- a/media/java/android/media/RouteDiscoveryRequest.aidl +++ b/media/java/android/media/RouteDiscoveryPreference.aidl @@ -16,4 +16,4 @@ package android.media; -parcelable RouteDiscoveryRequest; +parcelable RouteDiscoveryPreference; diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java new file mode 100644 index 000000000000..7ec1123ee19c --- /dev/null +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -0,0 +1,215 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A media route discovery preference describing the kinds of routes that media router + * would like to discover and whether to perform active scanning. + * + * @see MediaRouter2#registerRouteCallback + */ +public final class RouteDiscoveryPreference implements Parcelable { + @NonNull + public static final Creator<RouteDiscoveryPreference> CREATOR = + new Creator<RouteDiscoveryPreference>() { + @Override + public RouteDiscoveryPreference createFromParcel(Parcel in) { + return new RouteDiscoveryPreference(in); + } + + @Override + public RouteDiscoveryPreference[] newArray(int size) { + return new RouteDiscoveryPreference[size]; + } + }; + + @NonNull + private final List<String> mPreferredFeatures; + private final boolean mActiveScan; + @Nullable + private final Bundle mExtras; + + /** + * @hide + */ + public static final RouteDiscoveryPreference EMPTY = + new Builder(Collections.emptyList(), false).build(); + + RouteDiscoveryPreference(@NonNull Builder builder) { + mPreferredFeatures = builder.mPreferredFeatures; + mActiveScan = builder.mActiveScan; + mExtras = builder.mExtras; + } + + RouteDiscoveryPreference(@NonNull Parcel in) { + mPreferredFeatures = in.createStringArrayList(); + mActiveScan = in.readBoolean(); + mExtras = in.readBundle(); + } + + @NonNull + public List<String> getPreferredFeatures() { + return mPreferredFeatures; + } + + public boolean isActiveScan() { + return mActiveScan; + } + + /** + * @hide + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStringList(mPreferredFeatures); + dest.writeBoolean(mActiveScan); + dest.writeBundle(mExtras); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder() + .append("RouteDiscoveryRequest{ ") + .append("preferredFeatures={") + .append(String.join(", ", mPreferredFeatures)) + .append("}") + .append(", activeScan=") + .append(mActiveScan) + .append(" }"); + + return result.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RouteDiscoveryPreference)) { + return false; + } + RouteDiscoveryPreference other = (RouteDiscoveryPreference) o; + return Objects.equals(mPreferredFeatures, other.mPreferredFeatures) + && mActiveScan == other.mActiveScan; + } + + @Override + public int hashCode() { + return Objects.hash(mPreferredFeatures, mActiveScan); + } + + /** + * Builder for {@link RouteDiscoveryPreference}. + */ + public static final class Builder { + List<String> mPreferredFeatures; + boolean mActiveScan; + Bundle mExtras; + + public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) { + mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures, + "preferredFeatures must not be null")); + mActiveScan = activeScan; + } + + public Builder(@NonNull RouteDiscoveryPreference preference) { + Objects.requireNonNull(preference, "preference must not be null"); + + mPreferredFeatures = preference.getPreferredFeatures(); + mActiveScan = preference.isActiveScan(); + mExtras = preference.getExtras(); + } + + /** + * A constructor to combine all of the preferences into a single preference . + * It ignores extras of preferences. + * + * @hide + */ + public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) { + Objects.requireNonNull(preferences, "preferences must not be null"); + + Set<String> routeFeatureSet = new HashSet<>(); + mActiveScan = false; + for (RouteDiscoveryPreference preference : preferences) { + routeFeatureSet.addAll(preference.mPreferredFeatures); + mActiveScan |= preference.mActiveScan; + } + mPreferredFeatures = new ArrayList<>(routeFeatureSet); + } + + /** + * Sets preferred route features to discover. + */ + @NonNull + public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) { + mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures, + "preferredFeatures must not be null")); + return this; + } + + /** + * Sets if active scanning should be performed. + */ + @NonNull + public Builder setActiveScan(boolean activeScan) { + mActiveScan = activeScan; + return this; + } + + /** + * Sets the extras of the route. + * @hide + */ + @NonNull + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Builds the {@link RouteDiscoveryPreference}. + */ + @NonNull + public RouteDiscoveryPreference build() { + return new RouteDiscoveryPreference(this); + } + } +} diff --git a/media/java/android/media/RouteDiscoveryRequest.java b/media/java/android/media/RouteDiscoveryRequest.java deleted file mode 100644 index 88b31fb30ffc..000000000000 --- a/media/java/android/media/RouteDiscoveryRequest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - - -/** - * @hide - */ -public final class RouteDiscoveryRequest implements Parcelable { - @NonNull - public static final Creator<RouteDiscoveryRequest> CREATOR = - new Creator<RouteDiscoveryRequest>() { - @Override - public RouteDiscoveryRequest createFromParcel(Parcel in) { - return new RouteDiscoveryRequest(in); - } - - @Override - public RouteDiscoveryRequest[] newArray(int size) { - return new RouteDiscoveryRequest[size]; - } - }; - - @NonNull - private final List<String> mRouteTypes; - private final boolean mActiveScan; - @Nullable - private final Bundle mExtras; - - /** - * @hide - */ - public static final RouteDiscoveryRequest EMPTY = - new Builder(Collections.emptyList(), false).build(); - - RouteDiscoveryRequest(@NonNull Builder builder) { - mRouteTypes = builder.mRouteTypes; - mActiveScan = builder.mActiveScan; - mExtras = builder.mExtras; - } - - RouteDiscoveryRequest(@NonNull Parcel in) { - mRouteTypes = in.createStringArrayList(); - mActiveScan = in.readBoolean(); - mExtras = in.readBundle(); - } - - @NonNull - public List<String> getRouteTypes() { - return mRouteTypes; - } - - public boolean isActiveScan() { - return mActiveScan; - } - - /** - * @hide - */ - public Bundle getExtras() { - return mExtras; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeStringList(mRouteTypes); - dest.writeBoolean(mActiveScan); - dest.writeBundle(mExtras); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder() - .append("RouteDiscoveryRequest{ ") - .append("routeTypes={") - .append(String.join(", ", mRouteTypes)) - .append("}") - .append(", activeScan=") - .append(mActiveScan) - .append(" }"); - - return result.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof RouteDiscoveryRequest)) { - return false; - } - RouteDiscoveryRequest other = (RouteDiscoveryRequest) o; - return Objects.equals(mRouteTypes, other.mRouteTypes) - && mActiveScan == other.mActiveScan; - } - - /** - * Builder for {@link RouteDiscoveryRequest}. - */ - public static final class Builder { - List<String> mRouteTypes; - boolean mActiveScan; - Bundle mExtras; - - public Builder(@NonNull List<String> routeTypes, boolean activeScan) { - mRouteTypes = new ArrayList<>( - Objects.requireNonNull(routeTypes, "routeTypes must not be null")); - mActiveScan = activeScan; - } - - public Builder(@NonNull RouteDiscoveryRequest request) { - Objects.requireNonNull(request, "request must not be null"); - - mRouteTypes = request.getRouteTypes(); - mActiveScan = request.isActiveScan(); - mExtras = request.getExtras(); - } - - /** - * A constructor to combine all of the requests into a single request. - * It ignores extras of requests. - */ - Builder(@NonNull Collection<RouteDiscoveryRequest> requests) { - Set<String> routeTypeSet = new HashSet<>(); - mActiveScan = false; - for (RouteDiscoveryRequest request : requests) { - routeTypeSet.addAll(request.mRouteTypes); - mActiveScan |= request.mActiveScan; - } - mRouteTypes = new ArrayList<>(routeTypeSet); - } - - /** - * Sets route types to discover. - */ - public Builder setRouteTypes(@NonNull List<String> routeTypes) { - mRouteTypes = new ArrayList<>( - Objects.requireNonNull(routeTypes, "routeTypes must not be null")); - return this; - } - - /** - * Sets if active scanning should be performed. - */ - public Builder setActiveScan(boolean activeScan) { - mActiveScan = activeScan; - return this; - } - - /** - * Sets the extras of the route. - * @hide - */ - public Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the {@link RouteDiscoveryRequest}. - */ - public RouteDiscoveryRequest build() { - return new RouteDiscoveryRequest(this); - } - } -} diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RoutingSessionInfo.aidl index fb5d836da98e..7b8e3d912cc1 100644 --- a/media/java/android/media/RouteSessionInfo.aidl +++ b/media/java/android/media/RoutingSessionInfo.aidl @@ -16,4 +16,4 @@ package android.media; -parcelable RouteSessionInfo; +parcelable RoutingSessionInfo; diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 5330630ef3a9..96acf6cec5b4 100644 --- a/media/java/android/media/RouteSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -30,29 +30,28 @@ import java.util.List; import java.util.Objects; /** - * Describes a route session that is made when a media route is selected. + * Describes a routing session which is created when a media route is selected. * @hide */ -public class RouteSessionInfo implements Parcelable { - +public final class RoutingSessionInfo implements Parcelable { @NonNull - public static final Creator<RouteSessionInfo> CREATOR = - new Creator<RouteSessionInfo>() { + public static final Creator<RoutingSessionInfo> CREATOR = + new Creator<RoutingSessionInfo>() { @Override - public RouteSessionInfo createFromParcel(Parcel in) { - return new RouteSessionInfo(in); + public RoutingSessionInfo createFromParcel(Parcel in) { + return new RoutingSessionInfo(in); } @Override - public RouteSessionInfo[] newArray(int size) { - return new RouteSessionInfo[size]; + public RoutingSessionInfo[] newArray(int size) { + return new RoutingSessionInfo[size]; } }; - public static final String TAG = "RouteSessionInfo"; + private static final String TAG = "RoutingSessionInfo"; final String mId; final String mClientPackageName; - final String mRouteType; + final String mRouteFeature; @Nullable final String mProviderId; final List<String> mSelectedRoutes; @@ -62,14 +61,15 @@ public class RouteSessionInfo implements Parcelable { @Nullable final Bundle mControlHints; - RouteSessionInfo(@NonNull Builder builder) { + RoutingSessionInfo(@NonNull Builder builder) { Objects.requireNonNull(builder, "builder must not be null."); mId = builder.mId; mClientPackageName = builder.mClientPackageName; - mRouteType = builder.mRouteType; + mRouteFeature = builder.mRouteFeature; mProviderId = builder.mProviderId; + // TODO: Needs to check that the routes already have unique IDs. mSelectedRoutes = Collections.unmodifiableList( convertToUniqueRouteIds(builder.mSelectedRoutes)); mSelectableRoutes = Collections.unmodifiableList( @@ -82,12 +82,12 @@ public class RouteSessionInfo implements Parcelable { mControlHints = builder.mControlHints; } - RouteSessionInfo(@NonNull Parcel src) { + RoutingSessionInfo(@NonNull Parcel src) { Objects.requireNonNull(src, "src must not be null."); mId = ensureString(src.readString()); mClientPackageName = ensureString(src.readString()); - mRouteType = ensureString(src.readString()); + mRouteFeature = ensureString(src.readString()); mProviderId = src.readString(); mSelectedRoutes = ensureList(src.createStringArrayList()); @@ -113,18 +113,6 @@ public class RouteSessionInfo implements Parcelable { } /** - * Returns whether the session info is valid or not - * - * TODO in this CL: Remove this method. - */ - public boolean isValid() { - return !TextUtils.isEmpty(mId) - && !TextUtils.isEmpty(mClientPackageName) - && !TextUtils.isEmpty(mRouteType) - && mSelectedRoutes.size() > 0; - } - - /** * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have * unique IDs. * <p> @@ -160,12 +148,12 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets the route type of the session. - * Routes that don't have the type can't be added to the session. + * Gets the route feature of the session. + * Routes that don't have the feature can't be selected into the session. */ @NonNull - public String getRouteType() { - return mRouteType; + public String getRouteFeature() { + return mRouteFeature; } /** @@ -226,7 +214,7 @@ public class RouteSessionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mId); dest.writeString(mClientPackageName); - dest.writeString(mRouteType); + dest.writeString(mRouteFeature); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); dest.writeStringList(mSelectableRoutes); @@ -236,11 +224,37 @@ public class RouteSessionInfo implements Parcelable { } @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RoutingSessionInfo)) { + return false; + } + + RoutingSessionInfo other = (RoutingSessionInfo) obj; + return Objects.equals(mId, other.mId) + && Objects.equals(mClientPackageName, other.mClientPackageName) + && Objects.equals(mRouteFeature, other.mRouteFeature) + && Objects.equals(mProviderId, other.mProviderId) + && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) + && Objects.equals(mSelectableRoutes, other.mSelectableRoutes) + && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes) + && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes); + } + + @Override + public int hashCode() { + return Objects.hash(mId, mClientPackageName, mRouteFeature, mProviderId, + mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes); + } + + @Override public String toString() { StringBuilder result = new StringBuilder() - .append("RouteSessionInfo{ ") + .append("RoutingSessionInfo{ ") .append("sessionId=").append(mId) - .append(", routeType=").append(mRouteType) + .append(", routeFeature=").append(mRouteFeature) .append(", selectedRoutes={") .append(String.join(",", mSelectedRoutes)) .append("}") @@ -276,12 +290,12 @@ public class RouteSessionInfo implements Parcelable { } /** - * Builder class for {@link RouteSessionInfo}. + * Builder class for {@link RoutingSessionInfo}. */ public static final class Builder { final String mId; final String mClientPackageName; - final String mRouteType; + final String mRouteFeature; String mProviderId; final List<String> mSelectedRoutes; final List<String> mSelectableRoutes; @@ -290,24 +304,32 @@ public class RouteSessionInfo implements Parcelable { Bundle mControlHints; /** - * Constructor for builder to create {@link RouteSessionInfo}. + * Constructor for builder to create {@link RoutingSessionInfo}. * <p> * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of - * {@link RouteSessionInfo#getId()} can be different from what was set in + * {@link RoutingSessionInfo#getId()} can be different from what was set in * {@link MediaRoute2ProviderService}. * </p> * + * @param id ID of the session. Must not be empty. + * @param clientPackageName package name of the client app which uses this session. + * If is is unknown, then just use an empty string. + * @param routeFeature the route feature of session. Must not be empty. * @see MediaRoute2Info#getId() */ public Builder(@NonNull String id, @NonNull String clientPackageName, - @NonNull String routeType) { + @NonNull String routeFeature) { if (TextUtils.isEmpty(id)) { throw new IllegalArgumentException("id must not be empty"); } + Objects.requireNonNull(clientPackageName, "clientPackageName must not be null"); + if (TextUtils.isEmpty(routeFeature)) { + throw new IllegalArgumentException("routeFeature must not be empty"); + } + mId = id; - mClientPackageName = Objects.requireNonNull( - clientPackageName, "clientPackageName must not be null"); - mRouteType = Objects.requireNonNull(routeType, "routeType must not be null"); + mClientPackageName = clientPackageName; + mRouteFeature = routeFeature; mSelectedRoutes = new ArrayList<>(); mSelectableRoutes = new ArrayList<>(); mDeselectableRoutes = new ArrayList<>(); @@ -315,17 +337,17 @@ public class RouteSessionInfo implements Parcelable { } /** - * Constructor for builder to create {@link RouteSessionInfo} with - * existing {@link RouteSessionInfo} instance. + * Constructor for builder to create {@link RoutingSessionInfo} with + * existing {@link RoutingSessionInfo} instance. * * @param sessionInfo the existing instance to copy data from. */ - public Builder(@NonNull RouteSessionInfo sessionInfo) { + public Builder(@NonNull RoutingSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); mId = sessionInfo.mId; mClientPackageName = sessionInfo.mClientPackageName; - mRouteType = sessionInfo.mRouteType; + mRouteFeature = sessionInfo.mRouteFeature; mProviderId = sessionInfo.mProviderId; mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes); @@ -338,8 +360,6 @@ public class RouteSessionInfo implements Parcelable { /** * Sets the provider ID of the session. - * Also, calling this method will make all type of route IDs be unique by adding - * {@code providerId:} as a prefix. So do NOT call this method twice on same instance. * * @hide */ @@ -362,20 +382,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the selected routes. + * Adds a route to the selected routes. The {@code routeId} must not be empty. */ @NonNull public Builder addSelectedRoute(@NonNull String routeId) { - mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectedRoutes.add(routeId); return this; } /** - * Removes a route from the selected routes. + * Removes a route from the selected routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeSelectedRoute(@NonNull String routeId) { - mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectedRoutes.remove(routeId); return this; } @@ -389,20 +415,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the selectable routes. + * Adds a route to the selectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder addSelectableRoute(@NonNull String routeId) { - mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectableRoutes.add(routeId); return this; } /** - * Removes a route from the selectable routes. + * Removes a route from the selectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeSelectableRoute(@NonNull String routeId) { - mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectableRoutes.remove(routeId); return this; } @@ -416,20 +448,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the deselectable routes. + * Adds a route to the deselectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder addDeselectableRoute(@NonNull String routeId) { - mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mDeselectableRoutes.add(routeId); return this; } /** - * Removes a route from the deselectable routes. + * Removes a route from the deselectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeDeselectableRoute(@NonNull String routeId) { - mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mDeselectableRoutes.remove(routeId); return this; } @@ -443,21 +481,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the transferrable routes. + * Adds a route to the transferrable routes. The {@code routeId} must not be empty. */ @NonNull public Builder addTransferrableRoute(@NonNull String routeId) { - mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mTransferrableRoutes.add(routeId); return this; } /** - * Removes a route from the transferrable routes. + * Removes a route from the transferrable routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeTransferrableRoute(@NonNull String routeId) { - mTransferrableRoutes.remove( - Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mTransferrableRoutes.remove(routeId); return this; } @@ -471,11 +514,16 @@ public class RouteSessionInfo implements Parcelable { } /** - * Builds a route session info. + * Builds a routing session info. + * + * @throws IllegalArgumentException if no selected routes are added. */ @NonNull - public RouteSessionInfo build() { - return new RouteSessionInfo(this); + public RoutingSessionInfo build() { + if (mSelectedRoutes.isEmpty()) { + throw new IllegalArgumentException("selectedRoutes must not be empty"); + } + return new RoutingSessionInfo(this); } } } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 01f12500b77b..27f02fe528f3 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -719,9 +719,14 @@ public class AudioPolicy { if (track == null) { break; } - // TODO: add synchronous versions - track.stop(); - track.flush(); + try { + // TODO: add synchronous versions + track.stop(); + track.flush(); + } catch (IllegalStateException e) { + // ignore exception, AudioTrack could have already been stopped or + // released by the user of the AudioPolicy + } } } if (mCaptors != null) { @@ -730,8 +735,13 @@ public class AudioPolicy { if (record == null) { break; } - // TODO: if needed: implement an invalidate method - record.stop(); + try { + // TODO: if needed: implement an invalidate method + record.stop(); + } catch (IllegalStateException e) { + // ignore exception, AudioRecord could have already been stopped or + // released by the user of the AudioPolicy + } } } } diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java index 77596a5de815..118f65c214e1 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java +++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java @@ -25,6 +25,7 @@ import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.media.AudioFormat; import android.os.Handler; @@ -75,7 +76,9 @@ public final class SoundTriggerDetector { value = { RECOGNITION_FLAG_NONE, RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO, - RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS + RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS, + RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION, + RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, }) public @interface RecognitionFlags {} @@ -100,11 +103,34 @@ public final class SoundTriggerDetector { * triggers after a call to {@link #startRecognition(int)}, if the model * triggers multiple times. * When this isn't specified, the default behavior is to stop recognition once the - * trigger happenss, till the caller starts recognition again. + * trigger happens, till the caller starts recognition again. */ public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2; /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use AEC. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link SoundTriggerManager#getModuleProperties()} and checking + * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for + * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}. + * If this flag is passed without the audio capability supported, there will be no audio effect + * applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4; + + /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use noise suppression. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link SoundTriggerManager#getModuleProperties()} and checking + * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for + * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag + * is passed without the audio capability supported, there will be no audio effect applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; + + /** * Additional payload for {@link Callback#onDetected}. */ public static class EventPayload { @@ -267,11 +293,20 @@ public final class SoundTriggerDetector { boolean allowMultipleTriggers = (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; - int status = STATUS_OK; + + int audioCapabilities = 0; + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { + audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + } + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) { + audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + } + + int status; try { status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId), mRecognitionCallback, new RecognitionConfig(captureTriggerAudio, - allowMultipleTriggers, null, null)); + allowMultipleTriggers, null, null, audioCapabilities)); } catch (RemoteException e) { return false; } diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 1c38301c7935..dd4dac223a86 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -44,6 +44,7 @@ import com.android.internal.app.ISoundTriggerService; import com.android.internal.util.Preconditions; import java.util.HashMap; +import java.util.Objects; import java.util.UUID; /** @@ -175,19 +176,40 @@ public final class SoundTriggerManager { * Factory constructor to create a SoundModel instance for use with methods in this * class. */ - public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) { - return new Model(new SoundTrigger.GenericSoundModel(modelUuid, - vendorUuid, data)); + @NonNull + public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, int version) { + Objects.requireNonNull(modelUuid); + Objects.requireNonNull(vendorUuid); + return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data, + version)); } + /** + * Factory constructor to create a SoundModel instance for use with methods in this + * class. + */ + @NonNull + public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, + @Nullable byte[] data) { + return create(modelUuid, vendorUuid, data, -1); + } + + @NonNull public UUID getModelUuid() { return mGenericSoundModel.uuid; } + @NonNull public UUID getVendorUuid() { return mGenericSoundModel.vendorUuid; } + public int getVersion() { + return mGenericSoundModel.version; + } + + @Nullable public byte[] getModelData() { return mGenericSoundModel.data; } @@ -428,8 +450,7 @@ public final class SoundTriggerManager { */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable UUID soundModelId, - @ModelParams int modelParam, int value) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam, int value) { try { return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam, value); @@ -449,15 +470,10 @@ public final class SoundTriggerManager { * @param soundModelId UUID of model to get parameter * @param modelParam {@link ModelParams} * @return value of parameter - * @throws UnsupportedOperationException if hal or model do not support this API. - * {@link SoundTriggerManager#queryParameter} should be checked first. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - * {@link SoundTriggerManager#queryParameter} should be checked first. */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull UUID soundModelId, - @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam) { try { return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { @@ -479,8 +495,7 @@ public final class SoundTriggerManager { public ModelParamRange queryParameter(@Nullable UUID soundModelId, @ModelParams int modelParam) { try { - return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), - modelParam); + return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl new file mode 100644 index 000000000000..97a8849c7b07 --- /dev/null +++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.media.soundtrigger_middleware; + +/** + * AudioCapabilities supported by the implemented HAL driver. + * @hide + */ +@Backing(type="int") +enum AudioCapabilities { + /** + * If set the underlying module supports AEC. + */ + ECHO_CANCELLATION = 1 << 0, + /** + * If set, the underlying module supports noise suppression. + */ + NOISE_SUPPRESSION = 1 << 1, +} diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl index c7642e8c1241..5c0eeb1e32b1 100644 --- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl +++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl @@ -28,6 +28,12 @@ parcelable RecognitionConfig { /* Configuration for each key phrase. */ PhraseRecognitionExtra[] phraseRecognitionExtras; + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + */ + int audioCapabilities; + /** Opaque capture configuration data. */ byte[] data; } diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl index 1a3b40261a62..9c56e7b98b3f 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl +++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl @@ -30,6 +30,14 @@ parcelable SoundTriggerModuleProperties { * Unique implementation ID. The UUID must change with each version of the engine implementation */ String uuid; + /** + * String naming the architecture used for running the supported models. + * (eg. a platform running models on a DSP could implement this string to convey the DSP + * architecture used) + * This property is supported for soundtrigger HAL v2.3 and above. + * If running a previous version, the string will be empty. + */ + String supportedModelArch; /** Maximum number of concurrent sound models loaded */ int maxSoundModels; /** Maximum number of key phrases */ @@ -50,4 +58,11 @@ parcelable SoundTriggerModuleProperties { * Rated power consumption when detection is active with TDB * silence/sound/speech ratio */ int powerConsumptionMw; + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + * This property is supported for soundtrigger HAL v2.3 and above. + * If running a previous version, this value will be 0. + */ + int audioCapabilities; } diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl index d8f9d8f7e891..5d082e272295 100644 --- a/media/java/android/media/soundtrigger_middleware/Status.aidl +++ b/media/java/android/media/soundtrigger_middleware/Status.aidl @@ -26,4 +26,6 @@ enum Status { RESOURCE_CONTENTION = 1, /** Operation is not supported in this implementation. This is a permanent condition. */ OPERATION_NOT_SUPPORTED = 2, + /** Temporary lack of permission. */ + TEMPORARY_PERMISSION_DENIED = 3, } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index b076bb67738f..b5e9d1b2939f 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -60,6 +60,7 @@ interface ITvInputManager { void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession, int seq, int userId); void releaseSession(in IBinder sessionToken, int userId); + int getClientPid(in String sessionId); void setMainSession(in IBinder sessionToken, int userId); void setSurface(in IBinder sessionToken, in Surface surface, int userId); diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl index f90c50491129..8ccf13ae2d72 100755 --- a/media/java/android/media/tv/ITvInputService.aidl +++ b/media/java/android/media/tv/ITvInputService.aidl @@ -30,8 +30,9 @@ oneway interface ITvInputService { void registerCallback(in ITvInputServiceCallback callback); void unregisterCallback(in ITvInputServiceCallback callback); void createSession(in InputChannel channel, in ITvInputSessionCallback callback, - in String inputId); - void createRecordingSession(in ITvInputSessionCallback callback, in String inputId); + in String inputId, in String sessionId); + void createRecordingSession(in ITvInputSessionCallback callback, in String inputId, + in String sessionId); // For hardware TvInputService void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 854ea43f17c3..630d8191eff0 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -266,6 +266,15 @@ public final class TvInputManager { public static final int INPUT_STATE_DISCONNECTED = 2; /** + * An unknown state of the client pid gets from the TvInputManager. Client gets this value when + * query through {@link getClientPid(String sessionId)} fails. + * + * @hide + */ + @SystemApi + public static final int UNKNOWN_CLIENT_PID = -1; + + /** * Broadcast intent action when the user blocked content ratings change. For use with the * {@link #isRatingBlocked}. */ @@ -1484,6 +1493,21 @@ public final class TvInputManager { } /** + * Get a the client pid when creating the session with the session id provided. + * + * @param sessionId a String of session id that is used to query the client pid. + * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_PID} + * if the call fails. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) + public int getClientPid(@NonNull String sessionId) { + return getClientPidInternal(sessionId); + }; + + /** * Creates a recording {@link Session} for a given TV input. * * <p>The number of sessions that can be created at the same time is limited by the capability @@ -1516,6 +1540,17 @@ public final class TvInputManager { } } + private int getClientPidInternal(String sessionId) { + Preconditions.checkNotNull(sessionId); + int clientPid = UNKNOWN_CLIENT_PID; + try { + clientPid = mService.getClientPid(sessionId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return clientPid; + } + /** * Returns the TvStreamConfig list of the given TV input. * diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 7fbb3376d5fb..629dc7ce7819 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -124,7 +124,7 @@ public abstract class TvInputService extends Service { @Override public void createSession(InputChannel channel, ITvInputSessionCallback cb, - String inputId) { + String inputId, String sessionId) { if (channel == null) { Log.w(TAG, "Creating session without input channel"); } @@ -135,17 +135,20 @@ public abstract class TvInputService extends Service { args.arg1 = channel; args.arg2 = cb; args.arg3 = inputId; + args.arg4 = sessionId; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget(); } @Override - public void createRecordingSession(ITvInputSessionCallback cb, String inputId) { + public void createRecordingSession(ITvInputSessionCallback cb, String inputId, + String sessionId) { if (cb == null) { return; } SomeArgs args = SomeArgs.obtain(); args.arg1 = cb; args.arg2 = inputId; + args.arg3 = sessionId; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args) .sendToTarget(); } @@ -208,6 +211,37 @@ public abstract class TvInputService extends Service { } /** + * Returns a concrete implementation of {@link Session}. + * + * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, + * it needs to override this method to get the sessionId passed. When no overriding, this method + * calls {@link #onCreateSession(String)} defaultly. + * + * @param inputId The ID of the TV input associated with the session. + * @param sessionId the unique sessionId created by TIF when session is created. + */ + @Nullable + public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) { + return onCreateSession(inputId); + } + + /** + * Returns a concrete implementation of {@link RecordingSession}. + * + * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, + * it needs to override this method to get the sessionId passed. When no overriding, this method + * calls {@link #onCreateRecordingSession(String)} defaultly. + * + * @param inputId The ID of the TV input associated with the recording session. + * @param sessionId the unique sessionId created by TIF when session is created. + */ + @Nullable + public RecordingSession onCreateRecordingSession( + @NonNull String inputId, @NonNull String sessionId) { + return onCreateRecordingSession(inputId); + } + + /** * Returns a new {@link TvInputInfo} object if this service is responsible for * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of * ignoring all hardware input. @@ -2032,8 +2066,9 @@ public abstract class TvInputService extends Service { InputChannel channel = (InputChannel) args.arg1; ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; String inputId = (String) args.arg3; + String sessionId = (String) args.arg4; args.recycle(); - Session sessionImpl = onCreateSession(inputId); + Session sessionImpl = onCreateSession(inputId, sessionId); if (sessionImpl == null) { try { // Failed to create a session. @@ -2103,8 +2138,10 @@ public abstract class TvInputService extends Service { SomeArgs args = (SomeArgs) msg.obj; ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1; String inputId = (String) args.arg2; + String sessionId = (String) args.arg3; args.recycle(); - RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId); + RecordingSession recordingSessionImpl = + onCreateRecordingSession(inputId, sessionId); if (recordingSessionImpl == null) { try { // Failed to create a recording session. diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index 4318a0ae7d06..5e0b1eab4393 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -318,7 +318,8 @@ public final class TvTrackInfo implements Parcelable { * @param flags The flags used for parceling. */ @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { + Preconditions.checkNotNull(dest); dest.writeInt(mType); dest.writeString(mId); dest.writeString(mLanguage); @@ -387,11 +388,13 @@ public final class TvTrackInfo implements Parcelable { public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR = new Parcelable.Creator<TvTrackInfo>() { @Override + @NonNull public TvTrackInfo createFromParcel(Parcel in) { return new TvTrackInfo(in); } @Override + @NonNull public TvTrackInfo[] newArray(int size) { return new TvTrackInfo[size]; } @@ -444,7 +447,9 @@ public final class TvTrackInfo implements Parcelable { * * @param language The language string encoded by either ISO 639-1 or ISO 639-2/T. */ - public final Builder setLanguage(String language) { + @NonNull + public Builder setLanguage(@NonNull String language) { + Preconditions.checkNotNull(language); mLanguage = language; return this; } @@ -454,7 +459,9 @@ public final class TvTrackInfo implements Parcelable { * * @param description The user readable description. */ - public final Builder setDescription(CharSequence description) { + @NonNull + public Builder setDescription(@NonNull CharSequence description) { + Preconditions.checkNotNull(description); mDescription = description; return this; } @@ -479,7 +486,8 @@ public final class TvTrackInfo implements Parcelable { * @param audioChannelCount The audio channel count. * @throws IllegalStateException if not called on an audio track */ - public final Builder setAudioChannelCount(int audioChannelCount) { + @NonNull + public Builder setAudioChannelCount(int audioChannelCount) { if (mType != TYPE_AUDIO) { throw new IllegalStateException("Not an audio track"); } @@ -494,7 +502,8 @@ public final class TvTrackInfo implements Parcelable { * @param audioSampleRate The audio sample rate. * @throws IllegalStateException if not called on an audio track */ - public final Builder setAudioSampleRate(int audioSampleRate) { + @NonNull + public Builder setAudioSampleRate(int audioSampleRate) { if (mType != TYPE_AUDIO) { throw new IllegalStateException("Not an audio track"); } @@ -570,7 +579,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoWidth The width of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoWidth(int videoWidth) { + @NonNull + public Builder setVideoWidth(int videoWidth) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -585,7 +595,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoHeight The height of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoHeight(int videoHeight) { + @NonNull + public Builder setVideoHeight(int videoHeight) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -600,7 +611,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoFrameRate The frame rate of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoFrameRate(float videoFrameRate) { + @NonNull + public Builder setVideoFrameRate(float videoFrameRate) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -620,7 +632,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoPixelAspectRatio The pixel aspect ratio of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { + @NonNull + public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -640,7 +653,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoActiveFormatDescription The AFD code of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) { + @NonNull + public Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -653,7 +667,9 @@ public final class TvTrackInfo implements Parcelable { * * @param extra The extra information. */ - public final Builder setExtra(Bundle extra) { + @NonNull + public Builder setExtra(@NonNull Bundle extra) { + Preconditions.checkNotNull(extra); mExtra = new Bundle(extra); return this; } @@ -663,6 +679,7 @@ public final class TvTrackInfo implements Parcelable { * * @return The new {@link TvTrackInfo} instance */ + @NonNull public TvTrackInfo build() { return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted, mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing, diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java index bda166ec9d14..83abf8616468 100644 --- a/media/java/android/media/tv/tuner/DemuxCapabilities.java +++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java @@ -16,11 +16,32 @@ package android.media.tv.tuner; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.Size; +import android.media.tv.tuner.filter.FilterConfiguration; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Capabilities info for Demux. + * * @hide */ public class DemuxCapabilities { + + /** @hide */ + @IntDef(flag = true, value = { + FilterConfiguration.FILTER_TYPE_TS, + FilterConfiguration.FILTER_TYPE_MMTP, + FilterConfiguration.FILTER_TYPE_IP, + FilterConfiguration.FILTER_TYPE_TLV, + FilterConfiguration.FILTER_TYPE_ALP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterCapabilities {} + private final int mNumDemux; private final int mNumRecord; private final int mNumPlayback; @@ -34,7 +55,8 @@ public class DemuxCapabilities { private final int mFilterCaps; private final int[] mLinkCaps; - DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter, + // Used by JNI + private DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter, int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter, int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) { mNumDemux = numDemux; @@ -51,52 +73,73 @@ public class DemuxCapabilities { mLinkCaps = linkCaps; } - /** Gets total number of demuxes. */ + /** + * Gets total number of demuxes. + */ public int getNumDemux() { return mNumDemux; } - /** Gets max number of recordings at a time. */ + /** + * Gets max number of recordings at a time. + */ public int getNumRecord() { return mNumRecord; } - /** Gets max number of playbacks at a time. */ + /** + * Gets max number of playbacks at a time. + */ public int getNumPlayback() { return mNumPlayback; } - /** Gets number of TS filters. */ + /** + * Gets number of TS filters. + */ public int getNumTsFilter() { return mNumTsFilter; } - /** Gets number of section filters. */ + /** + * Gets number of section filters. + */ public int getNumSectionFilter() { return mNumSectionFilter; } - /** Gets number of audio filters. */ + /** + * Gets number of audio filters. + */ public int getNumAudioFilter() { return mNumAudioFilter; } - /** Gets number of video filters. */ + /** + * Gets number of video filters. + */ public int getNumVideoFilter() { return mNumVideoFilter; } - /** Gets number of PES filters. */ + /** + * Gets number of PES filters. + */ public int getNumPesFilter() { return mNumPesFilter; } - /** Gets number of PCR filters. */ + /** + * Gets number of PCR filters. + */ public int getNumPcrFilter() { return mNumPcrFilter; } - /** Gets number of bytes in the mask of a section filter. */ + /** + * Gets number of bytes in the mask of a section filter. + */ public int getNumBytesInSectionFilter() { return mNumBytesInSectionFilter; } /** * Gets filter capabilities in bit field. * - * The bits of the returned value is corresponding to the types in - * {@link TunerConstants.FilterType}. + * <p>The bits of the returned value is corresponding to the types in + * {@link FilterConfiguration}. */ + @FilterCapabilities public int getFilterCapabilities() { return mFilterCaps; } @@ -104,10 +147,12 @@ public class DemuxCapabilities { /** * Gets link capabilities. * - * The returned array contains the same elements as the number of types in - * {@link TunerConstants.FilterType}. - * The ith element represents the filter's capability as the source for the ith type + * <p>The returned array contains the same elements as the number of types in + * {@link FilterConfiguration}. + * <p>The ith element represents the filter's capability as the source for the ith type. */ + @Nullable + @Size(5) public int[] getLinkCapabilities() { return mLinkCaps; } diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java index f9f7a22c3de8..0143582ab815 100644 --- a/media/java/android/media/tv/tuner/Descrambler.java +++ b/media/java/android/media/tv/tuner/Descrambler.java @@ -16,9 +16,12 @@ package android.media.tv.tuner; +import android.annotation.IntDef; import android.annotation.Nullable; import android.media.tv.tuner.Tuner.Filter; -import android.media.tv.tuner.TunerConstants.DemuxPidType; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * This class is used to interact with descramblers. @@ -26,9 +29,25 @@ import android.media.tv.tuner.TunerConstants.DemuxPidType; * <p> Descrambler is a hardware component used to descramble data. * * <p> This class controls the TIS interaction with Tuner HAL. + * * @hide */ public class Descrambler implements AutoCloseable { + /** @hide */ + @IntDef(prefix = "PID_TYPE_", value = {PID_TYPE_T, PID_TYPE_MMPT}) + @Retention(RetentionPolicy.SOURCE) + public @interface PidType {} + + /** + * Packet ID is used to specify packets in transport stream. + */ + public static final int PID_TYPE_T = 1; + /** + * Packet ID is used to specify packets in MMTP. + */ + public static final int PID_TYPE_MMPT = 2; + + private long mNativeContext; private native int nativeAddPid(int pidType, int pid, Filter filter); @@ -52,10 +71,8 @@ public class Descrambler implements AutoCloseable { * @param pid the PID of packets to start to be descrambled. * @param filter an optional filter instance to identify upper stream. * @return result status of the operation. - * - * @hide */ - public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) { return nativeAddPid(pidType, pid, filter); } @@ -68,10 +85,8 @@ public class Descrambler implements AutoCloseable { * @param pid the PID of packets to stop to be descrambled. * @param filter an optional filter instance to identify upper stream. * @return result status of the operation. - * - * @hide */ - public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) { return nativeRemovePid(pidType, pid, filter); } @@ -83,17 +98,13 @@ public class Descrambler implements AutoCloseable { * * @param keyToken the token to be used to link the key slot. * @return result status of the operation. - * - * @hide */ - public int setKeyToken(byte[] keyToken) { + public int setKeyToken(@Nullable byte[] keyToken) { return nativeSetKeyToken(keyToken); } /** * Release the descrambler instance. - * - * @hide */ @Override public void close() { diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java deleted file mode 100644 index 76160dc9fcf8..000000000000 --- a/media/java/android/media/tv/tuner/DvrSettings.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.tv.tuner; - -import android.media.tv.tuner.TunerConstants.DataFormat; -import android.media.tv.tuner.TunerConstants.DvrSettingsType; - -/** - * DVR settings. - * - * @hide - */ -public class DvrSettings { - private int mStatusMask; - private int mLowThreshold; - private int mHighThreshold; - private int mPacketSize; - - @DataFormat - private int mDataFormat; - @DvrSettingsType - private int mType; - - private DvrSettings(int statusMask, int lowThreshold, int highThreshold, int packetSize, - @DataFormat int dataFormat, @DvrSettingsType int type) { - mStatusMask = statusMask; - mLowThreshold = lowThreshold; - mHighThreshold = highThreshold; - mPacketSize = packetSize; - mDataFormat = dataFormat; - mType = type; - } - - /** - * Creates a new builder. - */ - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Builder for DvrSettings. - */ - public static final class Builder { - private int mStatusMask; - private int mLowThreshold; - private int mHighThreshold; - private int mPacketSize; - @DataFormat - private int mDataFormat; - @DvrSettingsType - private int mType; - - /** - * Sets status mask. - */ - public Builder setStatusMask(int statusMask) { - this.mStatusMask = statusMask; - return this; - } - - /** - * Sets low threshold. - */ - public Builder setLowThreshold(int lowThreshold) { - this.mLowThreshold = lowThreshold; - return this; - } - - /** - * Sets high threshold. - */ - public Builder setHighThreshold(int highThreshold) { - this.mHighThreshold = highThreshold; - return this; - } - - /** - * Sets packet size. - */ - public Builder setPacketSize(int packetSize) { - this.mPacketSize = packetSize; - return this; - } - - /** - * Sets data format. - */ - public Builder setDataFormat(@DataFormat int dataFormat) { - this.mDataFormat = dataFormat; - return this; - } - - /** - * Sets settings type. - */ - public Builder setType(@DvrSettingsType int type) { - this.mType = type; - return this; - } - - /** - * Builds a DvrSettings instance. - */ - public DvrSettings build() { - return new DvrSettings( - mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType); - } - } -} diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java index e2e9910f53ee..ad8422caaa02 100644 --- a/media/java/android/media/tv/tuner/FrontendSettings.java +++ b/media/java/android/media/tv/tuner/FrontendSettings.java @@ -17,7 +17,6 @@ package android.media.tv.tuner; import android.annotation.SystemApi; -import android.media.tv.tuner.TunerConstants.FrontendSettingsType; /** * Frontend settings for tune and scan operations. @@ -35,7 +34,6 @@ public abstract class FrontendSettings { /** * Returns the frontend type. */ - @FrontendSettingsType public abstract int getType(); /** diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java new file mode 100644 index 000000000000..c7cc9e6ddb7f --- /dev/null +++ b/media/java/android/media/tv/tuner/Lnb.java @@ -0,0 +1,204 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.Tuner.LnbCallback; +import android.media.tv.tuner.TunerConstants.Result; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * LNB (low-noise block downconverter) for satellite tuner. + * + * A Tuner LNB (low-noise block downconverter) is used by satellite frontend to receive the + * microwave signal from the satellite, amplify it, and downconvert the frequency to a lower + * frequency. + * + * @hide + */ +@SystemApi +public class Lnb implements AutoCloseable { + /** @hide */ + @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V, + VOLTAGE_15V, VOLTAGE_18V, VOLTAGE_19V}) + @Retention(RetentionPolicy.SOURCE) + public @interface Voltage {} + + /** + * LNB power voltage not set. + */ + public static final int VOLTAGE_NONE = Constants.LnbVoltage.NONE; + /** + * LNB power voltage 5V. + */ + public static final int VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V; + /** + * LNB power voltage 11V. + */ + public static final int VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V; + /** + * LNB power voltage 12V. + */ + public static final int VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V; + /** + * LNB power voltage 13V. + */ + public static final int VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V; + /** + * LNB power voltage 14V. + */ + public static final int VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V; + /** + * LNB power voltage 15V. + */ + public static final int VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V; + /** + * LNB power voltage 18V. + */ + public static final int VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V; + /** + * LNB power voltage 19V. + */ + public static final int VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V; + + /** @hide */ + @IntDef({TONE_NONE, TONE_CONTINUOUS}) + @Retention(RetentionPolicy.SOURCE) + public @interface Tone {} + + /** + * LNB tone mode not set. + */ + public static final int TONE_NONE = Constants.LnbTone.NONE; + /** + * LNB continuous tone mode. + */ + public static final int TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS; + + /** @hide */ + @IntDef({POSITION_UNDEFINED, POSITION_A, POSITION_B}) + @Retention(RetentionPolicy.SOURCE) + public @interface Position {} + + /** + * LNB position is not defined. + */ + public static final int POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED; + /** + * Position A of two-band LNBs + */ + public static final int POSITION_A = Constants.LnbPosition.POSITION_A; + /** + * Position B of two-band LNBs + */ + public static final int POSITION_B = Constants.LnbPosition.POSITION_B; + + int mId; + LnbCallback mCallback; + Context mContext; + + private native int nativeSetVoltage(int voltage); + private native int nativeSetTone(int tone); + private native int nativeSetSatellitePosition(int position); + private native int nativeSendDiseqcMessage(byte[] message); + private native int nativeClose(); + + Lnb(int id) { + mId = id; + } + + /** @hide */ + public void setCallback(@Nullable LnbCallback callback) { + mCallback = callback; + if (mCallback == null) { + return; + } + } + + /** + * Sets the LNB's power voltage. + * + * @param voltage the power voltage constant the Lnb to use. + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int setVoltage(@Voltage int voltage) { + TunerUtils.checkTunerPermission(mContext); + return nativeSetVoltage(voltage); + } + + /** + * Sets the LNB's tone mode. + * + * @param tone the tone mode the Lnb to use. + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int setTone(@Tone int tone) { + TunerUtils.checkTunerPermission(mContext); + return nativeSetTone(tone); + } + + /** + * Selects the LNB's position. + * + * @param position the position the Lnb to use. + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int setSatellitePosition(@Position int position) { + TunerUtils.checkTunerPermission(mContext); + return nativeSetSatellitePosition(position); + } + + /** + * Sends DiSEqC (Digital Satellite Equipment Control) message. + * + * The response message from the device comes back through callback onDiseqcMessage. + * + * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus + * Functional Specification Version 4.2. + * + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int sendDiseqcMessage(@NonNull byte[] message) { + TunerUtils.checkTunerPermission(mContext); + return nativeSendDiseqcMessage(message); + } + + /** + * Releases the LNB instance. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + public void close() { + TunerUtils.checkTunerPermission(mContext); + nativeClose(); + } +} diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 962a7f6d58f6..e3dfaeb10948 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -16,6 +16,7 @@ package android.media.tv.tuner; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -24,20 +25,21 @@ import android.content.Context; import android.media.tv.tuner.TunerConstants.FilterStatus; import android.media.tv.tuner.TunerConstants.FilterSubtype; import android.media.tv.tuner.TunerConstants.FrontendScanType; -import android.media.tv.tuner.TunerConstants.LnbPosition; -import android.media.tv.tuner.TunerConstants.LnbTone; -import android.media.tv.tuner.TunerConstants.LnbVoltage; import android.media.tv.tuner.TunerConstants.Result; +import android.media.tv.tuner.dvr.Dvr; import android.media.tv.tuner.filter.FilterConfiguration.FilterType; import android.media.tv.tuner.filter.FilterEvent; +import android.media.tv.tuner.filter.TimeFilter; import android.media.tv.tuner.frontend.FrontendCallback; import android.media.tv.tuner.frontend.FrontendInfo; import android.media.tv.tuner.frontend.FrontendStatus; +import android.media.tv.tuner.frontend.ScanCallback; import android.os.Handler; import android.os.Looper; import android.os.Message; import java.util.List; +import java.util.concurrent.Executor; /** * This class is used to interact with hardware tuners devices. @@ -71,6 +73,10 @@ public final class Tuner implements AutoCloseable { private List<Integer> mLnbIds; private Lnb mLnb; + @Nullable + private ScanCallback mScanCallback; + @Nullable + private Executor mScanCallbackExecutor; /** * Constructs a Tuner instance. @@ -219,11 +225,6 @@ public final class Tuner implements AutoCloseable { } break; } - case MSG_ON_LNB_EVENT: { - if (mLnb != null && mLnb.mCallback != null) { - mLnb.mCallback.onEvent(msg.arg1); - } - } default: // fall through } @@ -237,29 +238,6 @@ public final class Tuner implements AutoCloseable { private Frontend(int id) { mId = id; } - - public void setCallback(@Nullable FrontendCallback callback, @Nullable Handler handler) { - mCallback = callback; - - if (mCallback == null) { - return; - } - - if (handler == null) { - // use default looper if handler is null - if (mHandler == null) { - mHandler = createEventHandler(); - } - return; - } - - Looper looper = handler.getLooper(); - if (mHandler != null && mHandler.getLooper() == looper) { - // the same looper. reuse mHandler - return; - } - mHandler = new EventHandler(looper); - } } /** @@ -292,18 +270,32 @@ public final class Tuner implements AutoCloseable { * Scan channels. * @hide */ - public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType) { + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType, + @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) { + mScanCallback = scanCallback; + mScanCallbackExecutor = executor; return nativeScan(settings.getType(), settings, scanType); } /** * Stops a previous scanning. * - * If the method completes successfully, the frontend stop previous scanning. + * <p> + * The {@link ScanCallback} and it's {@link Executor} will be removed. + * + * <p> + * If the method completes successfully, the frontend stopped previous scanning. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan() { - return nativeStopScan(); + TunerUtils.checkTunerPermission(mContext); + int retVal = nativeStopScan(); + mScanCallback = null; + mScanCallbackExecutor = null; + return retVal; } /** @@ -423,8 +415,14 @@ public final class Tuner implements AutoCloseable { return mFrontend.mId; } - /** @hide */ - private static DemuxCapabilities getDemuxCapabilities() { + /** + * Gets Demux capabilities. + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Nullable + public static DemuxCapabilities getDemuxCapabilities(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); return nativeGetDemuxCapabilities(); } @@ -474,102 +472,6 @@ public final class Tuner implements AutoCloseable { return filter; } - /** - * Open a time filter instance. - * - * It is used to open time filter of demux. - * - * @return a time filter instance. - * @hide - */ - public TimeFilter openTimeFilter() { - return nativeOpenTimeFilter(); - } - - /** @hide */ - public class Lnb { - private int mId; - private LnbCallback mCallback; - - private native int nativeSetVoltage(int voltage); - private native int nativeSetTone(int tone); - private native int nativeSetSatellitePosition(int position); - private native int nativeSendDiseqcMessage(byte[] message); - private native int nativeClose(); - - private Lnb(int id) { - mId = id; - } - - public void setCallback(@Nullable LnbCallback callback) { - mCallback = callback; - if (mCallback == null) { - return; - } - if (mHandler == null) { - mHandler = createEventHandler(); - } - } - - /** - * Sets the LNB's power voltage. - * - * @param voltage the power voltage the Lnb to use. - * @return result status of the operation. - */ - @Result - public int setVoltage(@LnbVoltage int voltage) { - return nativeSetVoltage(voltage); - } - - /** - * Sets the LNB's tone mode. - * - * @param tone the tone mode the Lnb to use. - * @return result status of the operation. - */ - @Result - public int setTone(@LnbTone int tone) { - return nativeSetTone(tone); - } - - /** - * Selects the LNB's position. - * - * @param position the position the Lnb to use. - * @return result status of the operation. - */ - @Result - public int setSatellitePosition(@LnbPosition int position) { - return nativeSetSatellitePosition(position); - } - - /** - * Sends DiSEqC (Digital Satellite Equipment Control) message. - * - * The response message from the device comes back through callback onDiseqcMessage. - * - * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus - * Functional Specification Version 4.2. - * - * @return result status of the operation. - */ - @Result - public int sendDiseqcMessage(byte[] message) { - return nativeSendDiseqcMessage(message); - } - - /** - * Releases the LNB instance - * - * @return result status of the operation. - */ - @Result - public int close() { - return nativeClose(); - } - } - private List<Integer> getLnbIds() { mLnbIds = nativeGetLnbIds(); return mLnbIds; diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index bbaa5180aece..4532122034ed 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -38,35 +38,6 @@ public final class TunerConstants { /** @hide */ - @IntDef({FRONTEND_TYPE_UNDEFINED, FRONTEND_TYPE_ANALOG, FRONTEND_TYPE_ATSC, FRONTEND_TYPE_ATSC3, - FRONTEND_TYPE_DVBC, FRONTEND_TYPE_DVBS, FRONTEND_TYPE_DVBT, FRONTEND_TYPE_ISDBS, - FRONTEND_TYPE_ISDBS3, FRONTEND_TYPE_ISDBT}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendType {} - - /** @hide */ - public static final int FRONTEND_TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED; - /** @hide */ - public static final int FRONTEND_TYPE_ANALOG = Constants.FrontendType.ANALOG; - /** @hide */ - public static final int FRONTEND_TYPE_ATSC = Constants.FrontendType.ATSC; - /** @hide */ - public static final int FRONTEND_TYPE_ATSC3 = Constants.FrontendType.ATSC3; - /** @hide */ - public static final int FRONTEND_TYPE_DVBC = Constants.FrontendType.DVBC; - /** @hide */ - public static final int FRONTEND_TYPE_DVBS = Constants.FrontendType.DVBS; - /** @hide */ - public static final int FRONTEND_TYPE_DVBT = Constants.FrontendType.DVBT; - /** @hide */ - public static final int FRONTEND_TYPE_ISDBS = Constants.FrontendType.ISDBS; - /** @hide */ - public static final int FRONTEND_TYPE_ISDBS3 = Constants.FrontendType.ISDBS3; - /** @hide */ - public static final int FRONTEND_TYPE_ISDBT = Constants.FrontendType.ISDBT; - - - /** @hide */ @IntDef({FRONTEND_EVENT_TYPE_LOCKED, FRONTEND_EVENT_TYPE_NO_SIGNAL, FRONTEND_EVENT_TYPE_LOST_LOCK}) @Retention(RetentionPolicy.SOURCE) @@ -80,54 +51,6 @@ public final class TunerConstants { /** @hide */ - @IntDef({DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV}) - @Retention(RetentionPolicy.SOURCE) - public @interface DataFormat {} - /** @hide */ - public static final int DATA_FORMAT_TS = Constants.DataFormat.TS; - /** @hide */ - public static final int DATA_FORMAT_PES = Constants.DataFormat.PES; - /** @hide */ - public static final int DATA_FORMAT_ES = Constants.DataFormat.ES; - /** @hide */ - public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV; - - - /** @hide */ - @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID}) - @Retention(RetentionPolicy.SOURCE) - public @interface DemuxPidType {} - /** @hide */ - public static final int DEMUX_T_PID = 1; - /** @hide */ - public static final int DEMUX_MMPT_PID = 2; - - /** @hide */ - @IntDef({FRONTEND_SETTINGS_ANALOG, FRONTEND_SETTINGS_ATSC, FRONTEND_SETTINGS_ATSC3, - FRONTEND_SETTINGS_DVBS, FRONTEND_SETTINGS_DVBC, FRONTEND_SETTINGS_DVBT, - FRONTEND_SETTINGS_ISDBS, FRONTEND_SETTINGS_ISDBS3, FRONTEND_SETTINGS_ISDBT}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendSettingsType {} - /** @hide */ - public static final int FRONTEND_SETTINGS_ANALOG = 1; - /** @hide */ - public static final int FRONTEND_SETTINGS_ATSC = 2; - /** @hide */ - public static final int FRONTEND_SETTINGS_ATSC3 = 3; - /** @hide */ - public static final int FRONTEND_SETTINGS_DVBS = 4; - /** @hide */ - public static final int FRONTEND_SETTINGS_DVBC = 5; - /** @hide */ - public static final int FRONTEND_SETTINGS_DVBT = 6; - /** @hide */ - public static final int FRONTEND_SETTINGS_ISDBS = 7; - /** @hide */ - public static final int FRONTEND_SETTINGS_ISDBS3 = 8; - /** @hide */ - public static final int FRONTEND_SETTINGS_ISDBT = 9; - - /** @hide */ @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES, FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD, FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI, @@ -171,8 +94,8 @@ public final class TunerConstants { public static final int FILTER_SUBTYPE_PTP = 16; /** @hide */ - @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, - FILTER_STATUS_OVERFLOW}) + @IntDef(flag = true, prefix = "FILTER_STATUS_", value = {FILTER_STATUS_DATA_READY, + FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, FILTER_STATUS_OVERFLOW}) @Retention(RetentionPolicy.SOURCE) public @interface FilterStatus {} @@ -201,6 +124,187 @@ public final class TunerConstants { */ public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW; + /** + * Indexes can be tagged through TS (Transport Stream) header. + * + * @hide + */ + @IntDef(flag = true, value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, + TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, + TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR, + TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, TS_INDEX_PCR_FLAG, + TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG, TS_INDEX_PRIVATE_DATA, + TS_INDEX_ADAPTATION_EXTENSION_FLAG}) + @Retention(RetentionPolicy.SOURCE) + public @interface TsIndex {} + + /** + * TS index FIRST_PACKET. + * @hide + */ + public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET; + /** + * TS index PAYLOAD_UNIT_START_INDICATOR. + * @hide + */ + public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR = + Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR; + /** + * TS index CHANGE_TO_NOT_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED; + /** + * TS index CHANGE_TO_EVEN_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED; + /** + * TS index CHANGE_TO_ODD_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED; + /** + * TS index DISCONTINUITY_INDICATOR. + * @hide + */ + public static final int TS_INDEX_DISCONTINUITY_INDICATOR = + Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR; + /** + * TS index RANDOM_ACCESS_INDICATOR. + * @hide + */ + public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR = + Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR; + /** + * TS index PRIORITY_INDICATOR. + * @hide + */ + public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR; + /** + * TS index PCR_FLAG. + * @hide + */ + public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG; + /** + * TS index OPCR_FLAG. + * @hide + */ + public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG; + /** + * TS index SPLICING_POINT_FLAG. + * @hide + */ + public static final int TS_INDEX_SPLICING_POINT_FLAG = + Constants.DemuxTsIndex.SPLICING_POINT_FLAG; + /** + * TS index PRIVATE_DATA. + * @hide + */ + public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA; + /** + * TS index ADAPTATION_EXTENSION_FLAG. + * @hide + */ + public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = + Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG; + + /** + * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream) + * according to ISO/IEC 13818-1. + * @hide + */ + @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME, + SC_INDEX_SEQUENCE}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScIndex {} + + /** + * SC index for a new I-frame. + * @hide + */ + public static final int SC_INDEX_I_FRAME = Constants.DemuxScIndex.I_FRAME; + /** + * SC index for a new P-frame. + * @hide + */ + public static final int SC_INDEX_P_FRAME = Constants.DemuxScIndex.P_FRAME; + /** + * SC index for a new B-frame. + * @hide + */ + public static final int SC_INDEX_B_FRAME = Constants.DemuxScIndex.B_FRAME; + /** + * SC index for a new sequence. + * @hide + */ + public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE; + + + /** + * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2. + * + * @hide + */ + @IntDef(flag = true, + value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, + SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP, + SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP, + SC_HEVC_INDEX_SLICE_TRAIL_CRA}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScHevcIndex {} + + /** + * SC HEVC index SPS. + * @hide + */ + public static final int SC_HEVC_INDEX_SPS = Constants.DemuxScHevcIndex.SPS; + /** + * SC HEVC index AUD. + * @hide + */ + public static final int SC_HEVC_INDEX_AUD = Constants.DemuxScHevcIndex.AUD; + /** + * SC HEVC index SLICE_CE_BLA_W_LP. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP = + Constants.DemuxScHevcIndex.SLICE_CE_BLA_W_LP; + /** + * SC HEVC index SLICE_BLA_W_RADL. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL = + Constants.DemuxScHevcIndex.SLICE_BLA_W_RADL; + /** + * SC HEVC index SLICE_BLA_N_LP. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP = + Constants.DemuxScHevcIndex.SLICE_BLA_N_LP; + /** + * SC HEVC index SLICE_IDR_W_RADL. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL = + Constants.DemuxScHevcIndex.SLICE_IDR_W_RADL; + /** + * SC HEVC index SLICE_IDR_N_LP. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP = + Constants.DemuxScHevcIndex.SLICE_IDR_N_LP; + /** + * SC HEVC index SLICE_TRAIL_CRA. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA = + Constants.DemuxScHevcIndex.SLICE_TRAIL_CRA; + + /** @hide */ @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND}) @Retention(RetentionPolicy.SOURCE) @@ -727,73 +831,6 @@ public final class TunerConstants { Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH; /** @hide */ - @IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM, - FRONTEND_ANALOG_TYPE_NTSC}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAnalogType {} - /** @hide */ - public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL; - /** @hide */ - public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM; - /** @hide */ - public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC; - - /** @hide */ - @IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2, - FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK, - FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3, - FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M, - FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J, - FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAnalogSifStandard {} - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_UNDEFINED = - Constants.FrontendAnalogSifStandard.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_BG_NICAM = - Constants.FrontendAnalogSifStandard.BG_NICAM; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_DK_NICAM = - Constants.FrontendAnalogSifStandard.DK_NICAM; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_M_EIA_J = - Constants.FrontendAnalogSifStandard.M_EIA_J; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_I_NICAM = - Constants.FrontendAnalogSifStandard.I_NICAM; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_L_NICAM = - Constants.FrontendAnalogSifStandard.L_NICAM; - /** @hide */ - public static final int FRONTEND_ANALOG_SIF_L_PRIME = - Constants.FrontendAnalogSifStandard.L_PRIME; - - /** @hide */ @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO, FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB}) @Retention(RetentionPolicy.SOURCE) @@ -978,10 +1015,13 @@ public final class TunerConstants { Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET; /** @hide */ - @IntDef({FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, FRONTEND_DVBS_STANDARD_S2, - FRONTEND_DVBS_STANDARD_S2X}) + @IntDef(prefix = "FRONTEND_DVBS_STANDARD", + value = {FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, + FRONTEND_DVBS_STANDARD_S2, + FRONTEND_DVBS_STANDARD_S2X}) @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbsStandard {} + public @interface FrontendDvbsStandard { + } /** @hide */ public static final int FRONTEND_DVBS_STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO; /** @hide */ @@ -1173,6 +1213,20 @@ public final class TunerConstants { Constants.FrontendDvbtGuardInterval.INTERVAL_19_256; /** @hide */ + @IntDef(prefix = "FRONTEND_DVBT_STANDARD", + value = {FRONTEND_DVBT_STANDARD_AUTO, FRONTEND_DVBT_STANDARD_T, + FRONTEND_DVBT_STANDARD_T2} + ) + @Retention(RetentionPolicy.SOURCE) + public @interface FrontendDvbtStandard {} + /** @hide */ + public static final int FRONTEND_DVBT_STANDARD_AUTO = Constants.FrontendDvbtStandard.AUTO; + /** @hide */ + public static final int FRONTEND_DVBT_STANDARD_T = Constants.FrontendDvbtStandard.T; + /** @hide */ + public static final int FRONTEND_DVBT_STANDARD_T2 = Constants.FrontendDvbtStandard.T2; + + /** @hide */ @IntDef({FRONTEND_ISDBS_CODERATE_UNDEFINED, FRONTEND_ISDBS_CODERATE_AUTO, FRONTEND_ISDBS_CODERATE_1_2, FRONTEND_ISDBS_CODERATE_2_3, FRONTEND_ISDBS_CODERATE_3_4, FRONTEND_ISDBS_CODERATE_5_6, FRONTEND_ISDBS_CODERATE_7_8}) @@ -1252,79 +1306,40 @@ public final class TunerConstants { /** @hide */ public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP; - /** @hide */ - @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK}) - @Retention(RetentionPolicy.SOURCE) - public @interface DvrSettingsType {} - /** @hide */ - public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD; - /** @hide */ - public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK; - - - /** @hide */ - @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_5V, LNB_VOLTAGE_11V, LNB_VOLTAGE_12V, LNB_VOLTAGE_13V, - LNB_VOLTAGE_14V, LNB_VOLTAGE_15V, LNB_VOLTAGE_18V, LNB_VOLTAGE_19V}) - @Retention(RetentionPolicy.SOURCE) - public @interface LnbVoltage {} - /** @hide */ - public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE; - /** @hide */ - public static final int LNB_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V; - /** @hide */ - public static final int LNB_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V; - /** @hide */ - public static final int LNB_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V; - /** @hide */ - public static final int LNB_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V; - /** @hide */ - public static final int LNB_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V; - /** @hide */ - public static final int LNB_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V; - /** @hide */ - public static final int LNB_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V; - /** @hide */ - public static final int LNB_VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V; - - /** @hide */ - @IntDef({LNB_TONE_NONE, LNB_TONE_CONTINUOUS}) - @Retention(RetentionPolicy.SOURCE) - public @interface LnbTone {} - /** @hide */ - public static final int LNB_TONE_NONE = Constants.LnbTone.NONE; - /** @hide */ - public static final int LNB_TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS; - - /** @hide */ - @IntDef({LNB_POSITION_UNDEFINED, LNB_POSITION_A, LNB_POSITION_B}) - @Retention(RetentionPolicy.SOURCE) - public @interface LnbPosition {} - /** @hide */ - public static final int LNB_POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED; - /** @hide */ - public static final int LNB_POSITION_A = Constants.LnbPosition.POSITION_A; - /** @hide */ - public static final int LNB_POSITION_B = Constants.LnbPosition.POSITION_B; - /** @hide */ @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE, RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR}) @Retention(RetentionPolicy.SOURCE) public @interface Result {} - /** @hide */ + + /** + * Operation succeeded. + */ public static final int RESULT_SUCCESS = Constants.Result.SUCCESS; - /** @hide */ + /** + * Operation failed because the corresponding resources are not available. + */ public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE; - /** @hide */ + /** + * Operation failed because the corresponding resources are not initialized. + */ public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED; - /** @hide */ + /** + * Operation failed because it's not in a valid state. + */ public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE; - /** @hide */ + /** + * Operation failed because there are invalid arguments. + */ public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT; - /** @hide */ + /** + * Memory allocation failed. + */ public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY; - /** @hide */ + /** + * Operation failed due to unknown errors. + */ public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR; private TunerConstants() { diff --git a/media/java/android/media/tv/tuner/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java index 0bfba8f9d4f3..f90042b8e745 100644 --- a/media/java/android/media/tv/tuner/Dvr.java +++ b/media/java/android/media/tv/tuner/dvr/Dvr.java @@ -14,14 +14,21 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.dvr; +import android.annotation.BytesLong; import android.annotation.NonNull; import android.media.tv.tuner.Tuner.DvrCallback; import android.media.tv.tuner.Tuner.Filter; +import android.media.tv.tuner.TunerConstants.Result; import android.os.ParcelFileDescriptor; -/** @hide */ +/** + * Digital Video Record (DVR) interface provides record control on Demux's output buffer and + * playback control on Demux's input buffer. + * + * @hide + */ public class Dvr { private long mNativeContext; private DvrCallback mCallback; @@ -34,10 +41,10 @@ public class Dvr { private native int nativeFlushDvr(); private native int nativeClose(); private native void nativeSetFileDescriptor(int fd); - private native int nativeRead(int size); - private native int nativeRead(byte[] bytes, int offset, int size); - private native int nativeWrite(int size); - private native int nativeWrite(byte[] bytes, int offset, int size); + private native int nativeRead(long size); + private native int nativeRead(byte[] bytes, long offset, long size); + private native int nativeWrite(long size); + private native int nativeWrite(byte[] bytes, long offset, long size); private Dvr() {} @@ -47,7 +54,8 @@ public class Dvr { * @param filter the filter to be attached. * @return result status of the operation. */ - public int attachFilter(Filter filter) { + @Result + public int attachFilter(@NonNull Filter filter) { return nativeAttachFilter(filter); } @@ -57,7 +65,8 @@ public class Dvr { * @param filter the filter to be detached. * @return result status of the operation. */ - public int detachFilter(Filter filter) { + @Result + public int detachFilter(@NonNull Filter filter) { return nativeDetachFilter(filter); } @@ -67,17 +76,19 @@ public class Dvr { * @param settings the settings of the DVR interface. * @return result status of the operation. */ - public int configure(DvrSettings settings) { + @Result + public int configure(@NonNull DvrSettings settings) { return nativeConfigureDvr(settings); } /** * Starts DVR. * - * Starts consuming playback data or producing data for recording. + * <p>Starts consuming playback data or producing data for recording. * * @return result status of the operation. */ + @Result public int start() { return nativeStartDvr(); } @@ -85,10 +96,11 @@ public class Dvr { /** * Stops DVR. * - * Stops consuming playback data or producing data for recording. + * <p>Stops consuming playback data or producing data for recording. * * @return result status of the operation. */ + @Result public int stop() { return nativeStopDvr(); } @@ -96,39 +108,53 @@ public class Dvr { /** * Flushed DVR data. * + * <p>The data in DVR buffer is cleared. + * * @return result status of the operation. */ + @Result public int flush() { return nativeFlushDvr(); } /** - * closes the DVR instance to release resources. + * Closes the DVR instance to release resources. * * @return result status of the operation. */ + @Result public int close() { return nativeClose(); } /** * Sets file descriptor to read/write data. + * + * @param fd the file descriptor to read/write data. */ - public void setFileDescriptor(ParcelFileDescriptor fd) { + public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) { nativeSetFileDescriptor(fd.getFd()); } /** * Reads data from the file for DVR playback. + * + * @param size the maximum number of bytes to read. + * @return the number of bytes read. */ - public int read(int size) { + public int read(@BytesLong long size) { return nativeRead(size); } /** - * Reads data from the buffer for DVR playback. + * Reads data from the buffer for DVR playback and copies to the given byte array. + * + * @param bytes the byte array to store the data. + * @param offset the index of the first byte in {@code bytes} to copy to. + * @param size the maximum number of bytes to read. + * @return the number of bytes read. */ - public int read(@NonNull byte[] bytes, int offset, int size) { + public int read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) { if (size + offset > bytes.length) { throw new ArrayIndexOutOfBoundsException( "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size); @@ -138,15 +164,23 @@ public class Dvr { /** * Writes recording data to file. + * + * @param size the maximum number of bytes to write. + * @return the number of bytes written. */ - public int write(int size) { + public int write(@BytesLong long size) { return nativeWrite(size); } /** * Writes recording data to buffer. + * + * @param bytes the byte array stores the data to be written to DVR. + * @param offset the index of the first byte in {@code bytes} to be written to DVR. + * @param size the maximum number of bytes to write. + * @return the number of bytes written. */ - public int write(@NonNull byte[] bytes, int offset, int size) { + public int write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) { return nativeWrite(bytes, offset, size); } } diff --git a/media/java/android/media/tv/tuner/dvr/DvrSettings.java b/media/java/android/media/tv/tuner/dvr/DvrSettings.java new file mode 100644 index 000000000000..46efd7a33e90 --- /dev/null +++ b/media/java/android/media/tv/tuner/dvr/DvrSettings.java @@ -0,0 +1,179 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.dvr; + +import android.annotation.BytesLong; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerConstants.FilterStatus; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * DVR settings used to configure {@link Dvr}. + * + * @hide + */ +public class DvrSettings { + + /** @hide */ + @IntDef(prefix = "DATA_FORMAT_", + value = {DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV}) + @Retention(RetentionPolicy.SOURCE) + public @interface DataFormat {} + + /** + * Transport Stream. + */ + public static final int DATA_FORMAT_TS = Constants.DataFormat.TS; + /** + * Packetized Elementary Stream. + */ + public static final int DATA_FORMAT_PES = Constants.DataFormat.PES; + /** + * Elementary Stream. + */ + public static final int DATA_FORMAT_ES = Constants.DataFormat.ES; + /** + * TLV (type-length-value) Stream for SHV + */ + public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV; + + + /** @hide */ + @IntDef(prefix = "TYPE_", value = {TYPE_RECORD, TYPE_PLAYBACK}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + /** + * DVR for recording. + */ + public static final int TYPE_RECORD = Constants.DvrType.RECORD; + /** + * DVR for playback of recorded programs. + */ + public static final int TYPE_PLAYBACK = Constants.DvrType.PLAYBACK; + + + + private final int mStatusMask; + private final long mLowThreshold; + private final long mHighThreshold; + private final long mPacketSize; + + @DataFormat + private final int mDataFormat; + @Type + private final int mType; + + private DvrSettings(int statusMask, long lowThreshold, long highThreshold, long packetSize, + @DataFormat int dataFormat, @Type int type) { + mStatusMask = statusMask; + mLowThreshold = lowThreshold; + mHighThreshold = highThreshold; + mPacketSize = packetSize; + mDataFormat = dataFormat; + mType = type; + } + + /** + * Creates a builder for {@link DvrSettings}. + */ + @NonNull + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder for {@link DvrSettings}. + */ + public static final class Builder { + private int mStatusMask; + private long mLowThreshold; + private long mHighThreshold; + private long mPacketSize; + @DataFormat + private int mDataFormat; + @Type + private int mType; + + /** + * Sets status mask. + */ + @NonNull + public Builder setStatusMask(@FilterStatus int statusMask) { + this.mStatusMask = statusMask; + return this; + } + + /** + * Sets low threshold in bytes. + */ + @NonNull + public Builder setLowThreshold(@BytesLong long lowThreshold) { + this.mLowThreshold = lowThreshold; + return this; + } + + /** + * Sets high threshold in bytes. + */ + @NonNull + public Builder setHighThreshold(@BytesLong long highThreshold) { + this.mHighThreshold = highThreshold; + return this; + } + + /** + * Sets packet size in bytes. + */ + @NonNull + public Builder setPacketSize(@BytesLong long packetSize) { + this.mPacketSize = packetSize; + return this; + } + + /** + * Sets data format. + */ + @NonNull + public Builder setDataFormat(@DataFormat int dataFormat) { + this.mDataFormat = dataFormat; + return this; + } + + /** + * Sets settings type. + */ + @NonNull + public Builder setType(@Type int type) { + this.mType = type; + return this; + } + + /** + * Builds a {@link DvrSettings} object. + */ + @NonNull + public DvrSettings build() { + return new DvrSettings( + mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType); + } + } +} diff --git a/media/java/android/media/tv/tuner/filter/AudioDescriptor.java b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java new file mode 100644 index 000000000000..c88c07f8a150 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java @@ -0,0 +1,106 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.filter; + +/** + * Meta data from AD (Audio Descriptor) according to ETSI TS 101 154 V2.1.1. + * + * @hide + */ +public class AudioDescriptor { + private final byte mAdFade; + private final byte mAdPan; + private final char mVersionTextTag; + private final byte mAdGainCenter; + private final byte mAdGainFront; + private final byte mAdGainSurround; + + // This constructor is used by JNI code only + private AudioDescriptor(byte adFade, byte adPan, char versionTextTag, byte adGainCenter, + byte adGainFront, byte adGainSurround) { + mAdFade = adFade; + mAdPan = adPan; + mVersionTextTag = versionTextTag; + mAdGainCenter = adGainCenter; + mAdGainFront = adGainFront; + mAdGainSurround = adGainSurround; + } + + /** + * Gets AD fade byte. + * + * <p>Takes values between 0x00 (representing no fade of the main programme sound) and 0xFF + * (representing a full fade). Over the range 0x00 to 0xFE one lsb represents a step in + * attenuation of the programme sound of 0.3 dB giving a range of 76.2 dB. The fade value of + * 0xFF represents no programme sound at all (i.e. mute). + */ + public byte getAdFade() { + return mAdFade; + } + + /** + * Gets AD pan byte. + * + * <p>Takes values between 0x00 representing a central forward presentation of the audio + * description and 0xFF, each increment representing a 360/256 degree step clockwise looking + * down on the listener (i.e. just over 1.4 degrees). + */ + public byte getAdPan() { + return mAdPan; + } + + /** + * Gets AD version tag. A single ASCII character version indicates the version. + * + * <p>A single ASCII character version designator (here "1" indicates revision 1). + */ + public char getVersionTextTag() { + return mVersionTextTag; + } + + /** + * Gets AD gain byte center in dB. + * + * <p>Represents a signed value in dB. Takes values between 0x7F (representing +76.2 dB boost of + * the main programme center) and 0x80 (representing a full fade). Over the range 0x00 to 0x7F + * one lsb represents a step in boost of the programme center of 0.6 dB giving a maximum boost + * of +76.2 dB. Over the range 0x81 to 0x00 one lsb represents a step in attenuation of the + * programme center of 0.6 dB giving a maximum attenuation of -76.2 dB. The gain value of 0x80 + * represents no main center level at all (i.e. mute). + */ + public byte getAdGainCenter() { + return mAdGainCenter; + } + + /** + * Gets AD gain byte front in dB. + * + * <p>Same as {@link #getAdGainCenter()}, but applied to left and right front channel. + */ + public byte getAdGainFront() { + return mAdGainFront; + } + + /** + * Gets AD gain byte surround in dB. + * + * <p>Same as {@link #getAdGainCenter()}, but applied to all surround channels + */ + public byte getAdGainSurround() { + return mAdGainSurround; + } +} diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java index 548fa777f765..591e4e54aaae 100644 --- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java +++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java @@ -16,15 +16,65 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * Download event. + * Filter event sent from {@link Filter} objects with download type. + * * @hide */ public class DownloadEvent extends FilterEvent { - private int mItemId; - private int mMpuSequenceNumber; - private int mItemFragmentIndex; - private int mLastItemFragmentIndex; - private int mDataLength; + private final int mItemId; + private final int mMpuSequenceNumber; + private final int mItemFragmentIndex; + private final int mLastItemFragmentIndex; + private final int mDataLength; + + // This constructor is used by JNI code only + private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex, + int lastItemFragmentIndex, int dataLength) { + mItemId = itemId; + mMpuSequenceNumber = mpuSequenceNumber; + mItemFragmentIndex = itemFragmentIndex; + mLastItemFragmentIndex = lastItemFragmentIndex; + mDataLength = dataLength; + } + + /** + * Gets item ID. + */ + public int getItemId() { + return mItemId; + } + + /** + * Gets MPU sequence number of filtered data. + */ + public int getMpuSequenceNumber() { + return mMpuSequenceNumber; + } + + /** + * Gets current index of the current item. + * + * An item can be stored in different fragments. + */ + public int getItemFragmentIndex() { + return mItemFragmentIndex; + } + + /** + * Gets last index of the current item. + */ + public int getLastItemFragmentIndex() { + return mLastItemFragmentIndex; + } + + /** + * Gets data size in bytes of filtered data. + */ + public int getDataLength() { + return mDataLength; + } } diff --git a/media/java/android/media/tv/tuner/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index db3b97afb1da..804c0c53982f 100644 --- a/media/java/android/media/tv/tuner/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -14,33 +14,33 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.filter; +import android.annotation.BytesLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.tv.tuner.Tuner.FilterCallback; -import android.media.tv.tuner.filter.FilterConfiguration; -import android.media.tv.tuner.filter.Settings; /** * Tuner data filter. * - * <p> This class is used to filter wanted data according to the filter's configuration. + * <p>This class is used to filter wanted data according to the filter's configuration. + * * @hide */ public class Filter implements AutoCloseable { private long mNativeContext; private FilterCallback mCallback; - int mId; + private final int mId; private native int nativeConfigureFilter( int type, int subType, FilterConfiguration settings); private native int nativeGetId(); - private native int nativeSetDataSource(Tuner.Filter source); + private native int nativeSetDataSource(Filter source); private native int nativeStartFilter(); private native int nativeStopFilter(); private native int nativeFlushFilter(); - private native int nativeRead(byte[] buffer, int offset, int size); + private native int nativeRead(byte[] buffer, long offset, long size); private native int nativeClose(); private Filter(int id) { @@ -53,24 +53,20 @@ public class Filter implements AutoCloseable { /** * Configures the filter. * - * @param settings the settings of the filter. + * @param config the configuration of the filter. * @return result status of the operation. - * @hide */ - public int configure(FilterConfiguration settings) { + public int configure(@NonNull FilterConfiguration config) { int subType = -1; - Settings s = settings.getSettings(); + Settings s = config.getSettings(); if (s != null) { subType = s.getType(); } - return nativeConfigureFilter(settings.getType(), subType, settings); + return nativeConfigureFilter(config.getType(), subType, config); } /** * Gets the filter Id. - * - * @return the hardware resource Id for the filter. - * @hide */ public int getId() { return nativeGetId(); @@ -87,17 +83,15 @@ public class Filter implements AutoCloseable { * @param source the filter instance which provides data input. Switch to * use demux as data source if the filter instance is NULL. * @return result status of the operation. - * @hide */ - public int setDataSource(@Nullable Tuner.Filter source) { + public int setDataSource(@Nullable Filter source) { return nativeSetDataSource(source); } /** - * Starts the filter. + * Starts filtering data. * * @return result status of the operation. - * @hide */ public int start() { return nativeStartFilter(); @@ -105,35 +99,38 @@ public class Filter implements AutoCloseable { /** - * Stops the filter. + * Stops filtering data. * * @return result status of the operation. - * @hide */ public int stop() { return nativeStopFilter(); } /** - * Flushes the filter. + * Flushes the filter. Data in filter buffer is cleared. * * @return result status of the operation. - * @hide */ public int flush() { return nativeFlushFilter(); } - /** @hide */ - public int read(@NonNull byte[] buffer, int offset, int size) { + /** + * Copies filtered data from filter buffer to the given byte array. + * + * @param buffer the buffer to store the filtered data. + * @param offset the index of the first byte in {@code buffer} to write. + * @param size the maximum number of bytes to read. + * @return the number of bytes read. + */ + public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) { size = Math.min(size, buffer.length - offset); return nativeRead(buffer, offset, size); } /** - * Release the Filter instance. - * - * @hide + * Releases the Filter instance. */ @Override public void close() { diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java index 99b10cde34f9..6496627cd1d4 100644 --- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java @@ -18,6 +18,7 @@ package android.media.tv.tuner.filter; import android.annotation.IntDef; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.hardware.tv.tuner.V1_0.Constants; import java.lang.annotation.Retention; @@ -28,6 +29,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi public abstract class FilterConfiguration { /** @hide */ @@ -57,7 +59,7 @@ public abstract class FilterConfiguration { public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; @Nullable - private final Settings mSettings; + /* package */ final Settings mSettings; /* package */ FilterConfiguration(Settings settings) { mSettings = settings; diff --git a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java index 4da1d21e3c44..09489ed86fef 100644 --- a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java +++ b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java @@ -16,10 +16,25 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * IP payload event. + * Filter event sent from {@link Filter} objects with IP payload type. + * * @hide */ public class IpPayloadEvent extends FilterEvent { - private int mDataLength; + private final int mDataLength; + + // This constructor is used by JNI code only + private IpPayloadEvent(int dataLength) { + mDataLength = dataLength; + } + + /** + * Gets data size in bytes of filtered data. + */ + public int getDataLength() { + return mDataLength; + } } diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java index 7703248535e5..37f94ae377ae 100644 --- a/media/java/android/media/tv/tuner/filter/MediaEvent.java +++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java @@ -16,20 +16,111 @@ package android.media.tv.tuner.filter; -import android.os.NativeHandle; +import android.annotation.Nullable; +import android.media.tv.tuner.Tuner.Filter; /** - * Media event. + * Filter event sent from {@link Filter} objects with media type. + * * @hide */ -public class MediaEvent extends FilterEvent { - private int mStreamId; - private boolean mIsPtsPresent; - private long mPts; - private int mDataLength; - private NativeHandle mHandle; - private boolean mIsSecureMemory; - private int mMpuSequenceNumber; - private boolean mIsPrivateData; - private AudioExtraMetaData mExtraMetaData; +public class MediaEvent extends FilterEvent{ + private final int mStreamId; + private final boolean mIsPtsPresent; + private final long mPts; + private final int mDataLength; + private final Object mLinearBuffer; + private final boolean mIsSecureMemory; + private final int mMpuSequenceNumber; + private final boolean mIsPrivateData; + private final AudioDescriptor mExtraMetaData; + + // This constructor is used by JNI code only + private MediaEvent(int streamId, boolean isPtsPresent, long pts, int dataLength, Object buffer, + boolean isSecureMemory, int mpuSequenceNumber, boolean isPrivateData, + AudioDescriptor extraMetaData) { + mStreamId = streamId; + mIsPtsPresent = isPtsPresent; + mPts = pts; + mDataLength = dataLength; + mLinearBuffer = buffer; + mIsSecureMemory = isSecureMemory; + mMpuSequenceNumber = mpuSequenceNumber; + mIsPrivateData = isPrivateData; + mExtraMetaData = extraMetaData; + } + + /** + * Gets stream ID. + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Returns whether PTS is present. + * + * @return {@code true} if PTS is present in PES header; {@code false} otherwise. + */ + public boolean getIsPtsPresent() { + return mIsPtsPresent; + } + + /** + * Gets PTS (Presentation Time Stamp) for audio or video frame. + */ + public long getPts() { + return mPts; + } + + /** + * Gets data size in bytes of audio or video frame. + */ + public int getDataLength() { + return mDataLength; + } + + /** + * Gets a linear buffer associated to the memory where audio or video data stays. + * TODO: use LinearBuffer when it's ready. + * + * @hide + */ + public Object getLinearBuffer() { + return mLinearBuffer; + } + + /** + * Returns whether the data is secure. + * + * @return {@code true} if the data is in secure area, and isn't mappable; + * {@code false} otherwise. + */ + public boolean getIsSecureMemory() { + return mIsSecureMemory; + } + + /** + * Gets MPU sequence number of filtered data. + */ + public int getMpuSequenceNumber() { + return mMpuSequenceNumber; + } + + /** + * Returns whether the data is private. + * + * @return {@code true} if the data is in private; {@code false} otherwise. + */ + public boolean getIsPrivateData() { + return mIsPrivateData; + } + + /** + * Gets audio extra metadata. + */ + @Nullable + public AudioDescriptor getExtraMetaData() { + return mExtraMetaData; + } } diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java index dbd8c77d7ea3..7f379944b207 100644 --- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java +++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java @@ -16,11 +16,34 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * MMPT record event. + * Filter event sent from {@link Filter} objects with MMTP type. + * * @hide */ public class MmtpRecordEvent extends FilterEvent { - private int mScHevcIndexMask; - private long mByteNumber; + private final int mScHevcIndexMask; + private final long mByteNumber; + + // This constructor is used by JNI code only + private MmtpRecordEvent(int scHevcIndexMask, long byteNumber) { + mScHevcIndexMask = scHevcIndexMask; + mByteNumber = byteNumber; + } + + /** + * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2. + */ + public int getScHevcIndexMask() { + return mScHevcIndexMask; + } + + /** + * Gets the byte number from beginning of the filter's output. + */ + public long getByteNumber() { + return mByteNumber; + } } diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java index 16536e225472..60251bf919ad 100644 --- a/media/java/android/media/tv/tuner/filter/PesEvent.java +++ b/media/java/android/media/tv/tuner/filter/PesEvent.java @@ -16,12 +16,43 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * PES event. + * Filter event sent from {@link Filter} objects with PES type. + * * @hide */ public class PesEvent extends FilterEvent { - private int mStreamId; - private int mDataLength; - private int mMpuSequenceNumber; + private final int mStreamId; + private final int mDataLength; + private final int mMpuSequenceNumber; + + // This constructor is used by JNI code only + private PesEvent(int streamId, int dataLength, int mpuSequenceNumber) { + mStreamId = streamId; + mDataLength = dataLength; + mMpuSequenceNumber = mpuSequenceNumber; + } + + /** + * Gets stream ID. + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Gets data size in bytes of filtered data. + */ + public int getDataLength() { + return mDataLength; + } + + /** + * Gets MPU sequence number of filtered data. + */ + public int getMpuSequenceNumber() { + return mMpuSequenceNumber; + } } diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java index f38abf12e120..bfa1f8c67d97 100644 --- a/media/java/android/media/tv/tuner/filter/PesSettings.java +++ b/media/java/android/media/tv/tuner/filter/PesSettings.java @@ -17,6 +17,9 @@ package android.media.tv.tuner.filter; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerUtils; import android.media.tv.tuner.filter.FilterConfiguration.FilterType; @@ -26,6 +29,7 @@ import android.media.tv.tuner.filter.FilterConfiguration.FilterType; * * @hide */ +@SystemApi public class PesSettings extends Settings { private final int mStreamId; private final boolean mIsRaw; @@ -37,12 +41,32 @@ public class PesSettings extends Settings { } /** + * Gets stream ID. + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Returns whether the data is raw. + * + * @return {@code true} if the data is raw. Filter sends onFilterStatus callback + * instead of onFilterEvent for raw data. {@code false} otherwise. + */ + public boolean isRaw() { + return mIsRaw; + } + + /** * Creates a builder for {@link PesSettings}. * * @param mainType the filter main type of the settings. + * @param context the context of the caller. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @NonNull - public static Builder newBuilder(@FilterType int mainType) { + public static Builder builder(@NonNull Context context, @FilterType int mainType) { + TunerUtils.checkTunerPermission(context); return new Builder(mainType); } @@ -70,13 +94,13 @@ public class PesSettings extends Settings { } /** - * Sets whether it's raw. + * Sets whether the data is raw. * * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback * instead of onFilterEvent for raw data. {@code false} otherwise. */ @NonNull - public Builder setIsRaw(boolean isRaw) { + public Builder setRaw(boolean isRaw) { mIsRaw = isRaw; return this; } diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java index 146aca74ce0e..91559260031c 100644 --- a/media/java/android/media/tv/tuner/filter/Settings.java +++ b/media/java/android/media/tv/tuner/filter/Settings.java @@ -16,11 +16,14 @@ package android.media.tv.tuner.filter; +import android.annotation.SystemApi; + /** * Settings for filters of different subtypes. * * @hide */ +@SystemApi public abstract class Settings { private final int mType; diff --git a/media/java/android/media/tv/tuner/filter/TemiEvent.java b/media/java/android/media/tv/tuner/filter/TemiEvent.java index 384160443a00..031fa5c4cb0e 100644 --- a/media/java/android/media/tv/tuner/filter/TemiEvent.java +++ b/media/java/android/media/tv/tuner/filter/TemiEvent.java @@ -16,12 +16,46 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.media.tv.tuner.Tuner.Filter; + /** - * TEMI event. + * Filter event sent from {@link Filter} objects for Timed External Media Information (TEMI) data. + * * @hide */ public class TemiEvent extends FilterEvent { - private long mPts; - private byte mDescrTag; - private byte[] mDescrData; + private final long mPts; + private final byte mDescrTag; + private final byte[] mDescrData; + + // This constructor is used by JNI code only + private TemiEvent(long pts, byte descrTag, byte[] descrData) { + mPts = pts; + mDescrTag = descrTag; + mDescrData = descrData; + } + + + /** + * Gets PTS (Presentation Time Stamp) for audio or video frame. + */ + public long getPts() { + return mPts; + } + + /** + * Gets TEMI descriptor tag. + */ + public byte getDescriptorTag() { + return mDescrTag; + } + + /** + * Gets TEMI descriptor. + */ + @NonNull + public byte[] getDescriptorData() { + return mDescrData; + } } diff --git a/media/java/android/media/tv/tuner/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java index 8bd0d26ce951..c9750040c605 100644 --- a/media/java/android/media/tv/tuner/TimeFilter.java +++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.filter; import android.annotation.Nullable; import android.media.tv.tuner.TunerConstants.Result; diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java index d0241b6aba09..5c38cfa70eb3 100644 --- a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java @@ -17,12 +17,18 @@ package android.media.tv.tuner.filter; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; /** * Filter configuration for a TS filter. * * @hide */ +@SystemApi public class TsFilterConfiguration extends FilterConfiguration { private final int mTpid; @@ -37,10 +43,28 @@ public class TsFilterConfiguration extends FilterConfiguration { } /** + * Gets the {@link Settings} object of this filter configuration. + */ + @Nullable + public Settings getSettings() { + return mSettings; + } + /** + * Gets Tag Protocol ID. + */ + public int getTpid() { + return mTpid; + } + + /** * Creates a builder for {@link TsFilterConfiguration}. + * + * @param context the context of the caller. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @NonNull - public static Builder newBuilder() { + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); return new Builder(); } @@ -51,6 +75,9 @@ public class TsFilterConfiguration extends FilterConfiguration { private Settings mSettings; private int mTpid; + private Builder() { + } + /** * Sets filter settings. * diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java index 875b5bd2980d..fa4dd72e3eda 100644 --- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java +++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java @@ -16,12 +16,84 @@ package android.media.tv.tuner.filter; +import android.annotation.IntDef; +import android.media.tv.tuner.Tuner.Filter; +import android.media.tv.tuner.TunerConstants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** - * TS record event. + * Filter event sent from {@link Filter} objects for TS record data. + * * @hide */ public class TsRecordEvent extends FilterEvent { - private int mTpid; - private int mIndexMask; - private long mByteNumber; + /** + * @hide + */ + @IntDef(flag = true, value = { + TunerConstants.TS_INDEX_FIRST_PACKET, + TunerConstants.TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, + TunerConstants.TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, + TunerConstants.TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, + TunerConstants.TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, + TunerConstants.TS_INDEX_DISCONTINUITY_INDICATOR, + TunerConstants.TS_INDEX_RANDOM_ACCESS_INDICATOR, + TunerConstants.TS_INDEX_PRIORITY_INDICATOR, + TunerConstants.TS_INDEX_PCR_FLAG, + TunerConstants.TS_INDEX_OPCR_FLAG, + TunerConstants.TS_INDEX_SPLICING_POINT_FLAG, + TunerConstants.TS_INDEX_PRIVATE_DATA, + TunerConstants.TS_INDEX_ADAPTATION_EXTENSION_FLAG, + TunerConstants.SC_INDEX_I_FRAME, + TunerConstants.SC_INDEX_P_FRAME, + TunerConstants.SC_INDEX_B_FRAME, + TunerConstants.SC_INDEX_SEQUENCE, + TunerConstants.SC_HEVC_INDEX_SPS, + TunerConstants.SC_HEVC_INDEX_AUD, + TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL, + TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL, + TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface IndexMask {} + + private final int mPid; + private final int mIndexMask; + private final long mByteNumber; + + // This constructor is used by JNI code only + private TsRecordEvent(int pid, int indexMask, long byteNumber) { + mPid = pid; + mIndexMask = indexMask; + mByteNumber = byteNumber; + } + + /** + * Gets packet ID. + */ + public int getTpid() { + return mPid; + } + + /** + * Gets index mask. + * + * <p>The index type is one of TS, SC, and SC-HEVC, and is set when configuring the filter. + */ + @IndexMask + public int getIndexMask() { + return mIndexMask; + } + + /** + * Gets the byte number from beginning of the filter's output. + */ + public long getByteNumber() { + return mByteNumber; + } } diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java index 16308ced2300..aec8ce895ab8 100644 --- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java @@ -16,33 +16,153 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.hardware.tv.tuner.V1_0.Constants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** - * Frontend settings for analog. + * Frontend settings for analog tuner. + * * @hide */ public class AnalogFrontendSettings extends FrontendSettings { - private int mAnalogType; - private int mSifStandard; + /** @hide */ + @IntDef(flag = true, value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM, + SIGNAL_TYPE_NTSC}) + @Retention(RetentionPolicy.SOURCE) + public @interface SignalType {} + + /** + * Undefined analog signal type. + */ + public static final int SIGNAL_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED; + /** + * PAL analog signal type. + */ + public static final int SIGNAL_TYPE_PAL = Constants.FrontendAnalogType.PAL; + /** + * SECM analog signal type. + */ + public static final int SIGNAL_TYPE_SECAM = Constants.FrontendAnalogType.SECAM; + /** + * NTSC analog signal type. + */ + public static final int SIGNAL_TYPE_NTSC = Constants.FrontendAnalogType.NTSC; + + + /** @hide */ + @IntDef(flag = true, value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK, + SIF_DK1, SIF_DK2, SIF_DK3, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2, + SIF_M_EIA_J, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME}) + @Retention(RetentionPolicy.SOURCE) + public @interface SifStandard {} + + /** + * Undefined Analog Standard Interchange Format (SIF). + */ + public static final int SIF_UNDEFINED = Constants.FrontendAnalogSifStandard.UNDEFINED; + /** + * BG Analog Standard Interchange Format (SIF). + */ + public static final int SIF_BG = Constants.FrontendAnalogSifStandard.BG; + /** + * BG-A2 Analog Standard Interchange Format (SIF). + */ + public static final int SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2; + /** + * BG-NICAM Analog Standard Interchange Format (SIF). + */ + public static final int SIF_BG_NICAM = Constants.FrontendAnalogSifStandard.BG_NICAM; + /** + * I Analog Standard Interchange Format (SIF). + */ + public static final int SIF_I = Constants.FrontendAnalogSifStandard.I; + /** + * DK Analog Standard Interchange Format (SIF). + */ + public static final int SIF_DK = Constants.FrontendAnalogSifStandard.DK; + /** + * DK1 Analog Standard Interchange Format (SIF). + */ + public static final int SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1; + /** + * DK2 Analog Standard Interchange Format (SIF). + */ + public static final int SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2; + /** + * DK3 Analog Standard Interchange Format (SIF). + */ + public static final int SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3; + /** + * DK-NICAM Analog Standard Interchange Format (SIF). + */ + public static final int SIF_DK_NICAM = Constants.FrontendAnalogSifStandard.DK_NICAM; + /** + * L Analog Standard Interchange Format (SIF). + */ + public static final int SIF_L = Constants.FrontendAnalogSifStandard.L; + /** + * M Analog Standard Interchange Format (SIF). + */ + public static final int SIF_M = Constants.FrontendAnalogSifStandard.M; + /** + * M-BTSC Analog Standard Interchange Format (SIF). + */ + public static final int SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC; + /** + * M-A2 Analog Standard Interchange Format (SIF). + */ + public static final int SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2; + /** + * M-EIA-J Analog Standard Interchange Format (SIF). + */ + public static final int SIF_M_EIA_J = Constants.FrontendAnalogSifStandard.M_EIA_J; + /** + * I-NICAM Analog Standard Interchange Format (SIF). + */ + public static final int SIF_I_NICAM = Constants.FrontendAnalogSifStandard.I_NICAM; + /** + * L-NICAM Analog Standard Interchange Format (SIF). + */ + public static final int SIF_L_NICAM = Constants.FrontendAnalogSifStandard.L_NICAM; + /** + * L-PRIME Analog Standard Interchange Format (SIF). + */ + public static final int SIF_L_PRIME = Constants.FrontendAnalogSifStandard.L_PRIME; + + + private final int mAnalogType; + private final int mSifStandard; @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_ANALOG; + return FrontendSettings.TYPE_ANALOG; } + + /** + * Gets analog signal type. + */ + @SignalType public int getAnalogType() { return mAnalogType; } + /** + * Gets Standard Interchange Format (SIF). + */ + @SifStandard public int getSifStandard() { return mSifStandard; } /** - * Creates a new builder object. + * Creates a builder for {@link AnalogFrontendSettings}. */ + @NonNull public static Builder newBuilder() { return new Builder(); } @@ -54,7 +174,7 @@ public class AnalogFrontendSettings extends FrontendSettings { } /** - * Builder for FrontendAnalogSettings. + * Builder for {@link AnalogFrontendSettings}. */ public static class Builder { private int mFrequency; @@ -64,8 +184,9 @@ public class AnalogFrontendSettings extends FrontendSettings { private Builder() {} /** - * Sets frequency. + * Sets frequency in Hz. */ + @NonNull public Builder setFrequency(int frequency) { mFrequency = frequency; return this; @@ -74,22 +195,25 @@ public class AnalogFrontendSettings extends FrontendSettings { /** * Sets analog type. */ - public Builder setAnalogType(int analogType) { + @NonNull + public Builder setAnalogType(@SignalType int analogType) { mAnalogType = analogType; return this; } /** - * Sets sif standard. + * Sets Standard Interchange Format (SIF). */ - public Builder setSifStandard(int sifStandard) { + @NonNull + public Builder setSifStandard(@SifStandard int sifStandard) { mSifStandard = sifStandard; return this; } /** - * Builds a FrontendAnalogSettings instance. + * Builds a {@link AnalogFrontendSettings} object. */ + @NonNull public AnalogFrontendSettings build() { return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard); } diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java index bce8a640c7f2..5b09e36ea113 100644 --- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java @@ -16,10 +16,6 @@ package android.media.tv.tuner.frontend; - -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - import java.util.List; /** @@ -37,6 +33,6 @@ public class Atsc3FrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_ATSC3; + return FrontendSettings.TYPE_ATSC3; } } diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java index 14c5cdd112e1..19e18d017e67 100644 --- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java @@ -16,9 +16,6 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - /** * Frontend settings for ATSC. * @hide @@ -32,6 +29,6 @@ public class AtscFrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_ATSC; + return FrontendSettings.TYPE_ATSC; } } diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java index 07e49ff24de1..60618f6f6896 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java @@ -16,9 +16,6 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - /** * Frontend settings for DVBC. * @hide @@ -37,6 +34,6 @@ public class DvbcFrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_DVBC; + return FrontendSettings.TYPE_DVBC; } } diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java index 23c0a7b15e52..586787f9eb73 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java @@ -16,9 +16,6 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - /** * Frontend settings for DVBS. * @hide @@ -38,6 +35,6 @@ public class DvbsFrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_DVBS; + return FrontendSettings.TYPE_DVBS; } } diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java index eec00f3fab80..6b350a7865a2 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java @@ -16,10 +16,6 @@ package android.media.tv.tuner.frontend; - -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - /** * Frontend settings for DVBT. * @hide @@ -45,6 +41,6 @@ public class DvbtFrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_DVBT; + return FrontendSettings.TYPE_DVBT; } } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java index 0992eb665330..9c4f4606a9bc 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java @@ -27,10 +27,4 @@ public interface FrontendCallback { * Invoked when there is a frontend event. */ void onEvent(int frontendEventType); - - /** - * Invoked when there is a scan message. - * @param msg - */ - void onScanMessage(ScanMessage msg); } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java index 5d03570eea80..99e8dd2fe31f 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java @@ -16,7 +16,7 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.TunerConstants.FrontendType; +import android.media.tv.tuner.frontend.FrontendSettings.Type; /** * Frontend info. @@ -54,7 +54,7 @@ public class FrontendInfo { return mId; } /** Gets frontend type. */ - @FrontendType + @Type public int getType() { return mType; } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java new file mode 100644 index 000000000000..210aef4163e5 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.frontend; + +import android.annotation.IntDef; +import android.hardware.tv.tuner.V1_0.Constants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Frontend settings for tune and scan operations. + * + * @hide + */ +public abstract class FrontendSettings { + /** @hide */ + @IntDef({TYPE_UNDEFINED, TYPE_ANALOG, TYPE_ATSC, TYPE_ATSC3, TYPE_DVBC, TYPE_DVBS, TYPE_DVBT, + TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + /** + * Undefined frontend type. + */ + public static final int TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED; + /** + * Analog frontend type. + */ + public static final int TYPE_ANALOG = Constants.FrontendType.ANALOG; + /** + * Advanced Television Systems Committee (ATSC) frontend type. + */ + public static final int TYPE_ATSC = Constants.FrontendType.ATSC; + /** + * Advanced Television Systems Committee 3.0 (ATSC-3) frontend type. + */ + public static final int TYPE_ATSC3 = Constants.FrontendType.ATSC3; + /** + * Digital Video Broadcasting-Cable (DVB-C) frontend type. + */ + public static final int TYPE_DVBC = Constants.FrontendType.DVBC; + /** + * Digital Video Broadcasting-Satellite (DVB-S) frontend type. + */ + public static final int TYPE_DVBS = Constants.FrontendType.DVBS; + /** + * Digital Video Broadcasting-Terrestrial (DVB-T) frontend type. + */ + public static final int TYPE_DVBT = Constants.FrontendType.DVBT; + /** + * Integrated Services Digital Broadcasting-Satellite (ISDB-S) frontend type. + */ + public static final int TYPE_ISDBS = Constants.FrontendType.ISDBS; + /** + * Integrated Services Digital Broadcasting-Satellite 3 (ISDB-S3) frontend type. + */ + public static final int TYPE_ISDBS3 = Constants.FrontendType.ISDBS3; + /** + * Integrated Services Digital Broadcasting-Terrestrial (ISDB-T) frontend type. + */ + public static final int TYPE_ISDBT = Constants.FrontendType.ISDBT; + + private final int mFrequency; + + /** @hide */ + public FrontendSettings(int frequency) { + mFrequency = frequency; + } + + /** + * Returns the frontend type. + */ + @Type + public abstract int getType(); + + /** + * Gets the frequency. + * + * @return the frequency in Hz. + */ + public int getFrequency() { + return mFrequency; + } + +} diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index 89ec536a1d6c..fb5d62afd2f2 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -16,13 +16,13 @@ package android.media.tv.tuner.frontend; +import android.media.tv.tuner.Lnb; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion; import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy; import android.media.tv.tuner.TunerConstants.FrontendInnerFec; import android.media.tv.tuner.TunerConstants.FrontendModulation; import android.media.tv.tuner.TunerConstants.FrontendStatusType; -import android.media.tv.tuner.TunerConstants.LnbVoltage; /** * Frontend status @@ -128,7 +128,7 @@ public class FrontendStatus { return (int) mValue; } /** Power Voltage Type for LNB. */ - @LnbVoltage + @Lnb.Voltage public int getLnbVoltage() { if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) { throw new IllegalStateException(); diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java index 736d0b199d29..45932a74a946 100644 --- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java @@ -16,9 +16,6 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - /** * Frontend settings for ISDBS-3. * @hide @@ -37,6 +34,6 @@ public class Isdbs3FrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_ISDBS3; + return FrontendSettings.TYPE_ISDBS3; } } diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java index 7fd5da78c600..e726a9a701f0 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java @@ -16,9 +16,6 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - /** * Frontend settings for ISDBS. * @hide @@ -37,6 +34,6 @@ public class IsdbsFrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_ISDBS; + return FrontendSettings.TYPE_ISDBS; } } diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java index 3f83267fe5b4..f2b7d2413911 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java @@ -16,10 +16,6 @@ package android.media.tv.tuner.frontend; - -import android.media.tv.tuner.FrontendSettings; -import android.media.tv.tuner.TunerConstants; - /** * Frontend settings for ISDBT. * @hide @@ -37,6 +33,6 @@ public class IsdbtFrontendSettings extends FrontendSettings { @Override public int getType() { - return TunerConstants.FRONTEND_TYPE_ISDBT; + return FrontendSettings.TYPE_ISDBT; } } diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java new file mode 100644 index 000000000000..8118fcc0e900 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.frontend; + +import android.media.tv.tuner.TunerConstants; + +/** + * Scan callback. + * + * @hide + */ +public interface ScanCallback { + /** Scan locked the signal. */ + void onLocked(boolean isLocked); + + /** Scan stopped. */ + void onEnd(boolean isEnd); + + /** scan progress percent (0..100) */ + void onProgress(int percent); + + /** Signal frequency in Hertz */ + void onFrequencyReport(int frequency); + + /** Symbols per second */ + void onSymbolRate(int rate); + + /** Locked Plp Ids for DVBT2 frontend. */ + void onPlpIds(int[] plpIds); + + /** Locked group Ids for DVBT2 frontend. */ + void onGroupIds(int[] groupIds); + + /** Stream Ids. */ + void onInputStreamIds(int[] inputStreamIds); + + /** Locked signal standard. */ + void onDvbsStandard(@TunerConstants.FrontendDvbsStandard int dvbsStandandard); + + /** Locked signal standard. */ + void onDvbtStandard(@TunerConstants.FrontendDvbtStandard int dvbtStandard); + + /** PLP status in a tuned frequency band for ATSC3 frontend. */ + void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos); + + /** PLP information for ATSC3. */ + class Atsc3PlpInfo { + private final int mPlpId; + private final boolean mLlsFlag; + + private Atsc3PlpInfo(int plpId, boolean llsFlag) { + mPlpId = plpId; + mLlsFlag = llsFlag; + } + + /** Gets PLP IDs. */ + public int getPlpId() { + return mPlpId; + } + + /** Gets LLS flag. */ + public boolean getLlsFlag() { + return mLlsFlag; + } + } +} diff --git a/media/java/android/media/tv/tuner/frontend/ScanMessage.java b/media/java/android/media/tv/tuner/frontend/ScanMessage.java deleted file mode 100644 index dd687dd2959c..000000000000 --- a/media/java/android/media/tv/tuner/frontend/ScanMessage.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.tv.tuner.frontend; - -import android.annotation.IntDef; -import android.hardware.tv.tuner.V1_0.Constants; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Message from frontend during scan operations. - * - * @hide - */ -public class ScanMessage { - - /** @hide */ - @IntDef({ - LOCKED, - END, - PROGRESS_PERCENT, - FREQUENCY, - SYMBOL_RATE, - PLP_IDS, - GROUP_IDS, - INPUT_STREAM_IDS, - STANDARD, - ATSC3_PLP_INFO - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - /** @hide */ - public static final int LOCKED = Constants.FrontendScanMessageType.LOCKED; - /** @hide */ - public static final int END = Constants.FrontendScanMessageType.END; - /** @hide */ - public static final int PROGRESS_PERCENT = Constants.FrontendScanMessageType.PROGRESS_PERCENT; - /** @hide */ - public static final int FREQUENCY = Constants.FrontendScanMessageType.FREQUENCY; - /** @hide */ - public static final int SYMBOL_RATE = Constants.FrontendScanMessageType.SYMBOL_RATE; - /** @hide */ - public static final int PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS; - /** @hide */ - public static final int GROUP_IDS = Constants.FrontendScanMessageType.GROUP_IDS; - /** @hide */ - public static final int INPUT_STREAM_IDS = Constants.FrontendScanMessageType.INPUT_STREAM_IDS; - /** @hide */ - public static final int STANDARD = Constants.FrontendScanMessageType.STANDARD; - /** @hide */ - public static final int ATSC3_PLP_INFO = Constants.FrontendScanMessageType.ATSC3_PLP_INFO; - - private final int mType; - private final Object mValue; - - private ScanMessage(int type, Object value) { - mType = type; - mValue = value; - } - - /** Gets scan message type. */ - @Type - public int getMessageType() { - return mType; - } - /** Message indicates whether frontend is locked or not. */ - public boolean getIsLocked() { - if (mType != LOCKED) { - throw new IllegalStateException(); - } - return (Boolean) mValue; - } - /** Message indicates whether the scan has reached the end or not. */ - public boolean getIsEnd() { - if (mType != END) { - throw new IllegalStateException(); - } - return (Boolean) mValue; - } - /** Progress message in percent. */ - public int getProgressPercent() { - if (mType != PROGRESS_PERCENT) { - throw new IllegalStateException(); - } - return (Integer) mValue; - } - /** Gets frequency. */ - public int getFrequency() { - if (mType != FREQUENCY) { - throw new IllegalStateException(); - } - return (Integer) mValue; - } - /** Gets symbol rate. */ - public int getSymbolRate() { - if (mType != SYMBOL_RATE) { - throw new IllegalStateException(); - } - return (Integer) mValue; - } - /** Gets PLP IDs. */ - public int[] getPlpIds() { - if (mType != PLP_IDS) { - throw new IllegalStateException(); - } - return (int[]) mValue; - } - /** Gets group IDs. */ - public int[] getGroupIds() { - if (mType != GROUP_IDS) { - throw new IllegalStateException(); - } - return (int[]) mValue; - } - /** Gets Input stream IDs. */ - public int[] getInputStreamIds() { - if (mType != INPUT_STREAM_IDS) { - throw new IllegalStateException(); - } - return (int[]) mValue; - } - /** Gets the DVB-T or DVB-S standard. */ - public int getStandard() { - if (mType != STANDARD) { - throw new IllegalStateException(); - } - return (int) mValue; - } - - /** Gets PLP information for ATSC3. */ - public Atsc3PlpInfo[] getAtsc3PlpInfos() { - if (mType != ATSC3_PLP_INFO) { - throw new IllegalStateException(); - } - return (Atsc3PlpInfo[]) mValue; - } - - /** PLP information for ATSC3. */ - public static class Atsc3PlpInfo { - private final int mPlpId; - private final boolean mLlsFlag; - - private Atsc3PlpInfo(int plpId, boolean llsFlag) { - mPlpId = plpId; - mLlsFlag = llsFlag; - } - - /** Gets PLP IDs. */ - public int getPlpId() { - return mPlpId; - } - /** Gets LLS flag. */ - public boolean getLlsFlag() { - return mLlsFlag; - } - } -} diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 536a061190d7..aeacd8f63cb0 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -19,6 +19,7 @@ cc_library_shared { "android_media_MediaProfiles.cpp", "android_media_MediaRecorder.cpp", "android_media_MediaSync.cpp", + "android_media_MediaTranscodeManager.cpp", "android_media_ResampleInputStream.cpp", "android_media_Streams.cpp", "android_media_SyncParams.cpp", diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 963b650292e4..5cb42a9a96cc 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1453,6 +1453,7 @@ extern int register_android_media_MediaProfiles(JNIEnv *env); extern int register_android_mtp_MtpDatabase(JNIEnv *env); extern int register_android_mtp_MtpDevice(JNIEnv *env); extern int register_android_mtp_MtpServer(JNIEnv *env); +extern int register_android_media_MediaTranscodeManager(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { @@ -1565,6 +1566,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } + if (register_android_media_MediaTranscodeManager(env) < 0) { + ALOGE("ERROR: MediaTranscodeManager native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/media/jni/android_media_MediaTranscodeManager.cpp b/media/jni/android_media_MediaTranscodeManager.cpp new file mode 100644 index 000000000000..0b4048c1170c --- /dev/null +++ b/media/jni/android_media_MediaTranscodeManager.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaTranscodeManager_JNI" + +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" + +#include <nativehelper/JNIHelp.h> +#include <utils/Log.h> + +namespace { + +// NOTE: Keep these enums in sync with their equivalents in MediaTranscodeManager.java. +enum { + ID_INVALID = -1 +}; + +enum { + EVENT_JOB_STARTED = 1, + EVENT_JOB_PROGRESSED = 2, + EVENT_JOB_FINISHED = 3, +}; + +enum { + RESULT_NONE = 1, + RESULT_SUCCESS = 2, + RESULT_ERROR = 3, + RESULT_CANCELED = 4, +}; + +struct { + jmethodID postEventFromNative; +} gMediaTranscodeManagerClassInfo; + +using namespace android; + +void android_media_MediaTranscodeManager_native_init(JNIEnv *env, jclass clazz) { + ALOGV("android_media_MediaTranscodeManager_native_init"); + + gMediaTranscodeManagerClassInfo.postEventFromNative = env->GetMethodID( + clazz, "postEventFromNative", "(IJI)V"); + LOG_ALWAYS_FATAL_IF(gMediaTranscodeManagerClassInfo.postEventFromNative == NULL, + "can't find android/media/MediaTranscodeManager.postEventFromNative"); +} + +jlong android_media_MediaTranscodeManager_requestUniqueJobID( + JNIEnv *env __unused, jobject thiz __unused) { + ALOGV("android_media_MediaTranscodeManager_reserveUniqueJobID"); + static std::atomic_int32_t sJobIDCounter{0}; + jlong id = (jlong)++sJobIDCounter; + return id; +} + +jboolean android_media_MediaTranscodeManager_enqueueTranscodingRequest( + JNIEnv *env, jobject thiz, jlong id, jobject request, jobject context __unused) { + ALOGV("android_media_MediaTranscodeManager_enqueueTranscodingRequest"); + if (!request) { + return ID_INVALID; + } + + env->CallVoidMethod(thiz, gMediaTranscodeManagerClassInfo.postEventFromNative, + EVENT_JOB_FINISHED, id, RESULT_ERROR); + return true; +} + +void android_media_MediaTranscodeManager_cancelTranscodingRequest( + JNIEnv *env __unused, jobject thiz __unused, jlong jobID __unused) { + ALOGV("android_media_MediaTranscodeManager_cancelTranscodingRequest"); +} + +const JNINativeMethod gMethods[] = { + { "native_init", "()V", + (void *)android_media_MediaTranscodeManager_native_init }, + { "native_requestUniqueJobID", "()J", + (void *)android_media_MediaTranscodeManager_requestUniqueJobID }, + { "native_enqueueTranscodingRequest", + "(JLandroid/media/MediaTranscodeManager$TranscodingRequest;Landroid/content/Context;)Z", + (void *)android_media_MediaTranscodeManager_enqueueTranscodingRequest }, + { "native_cancelTranscodingRequest", "(J)V", + (void *)android_media_MediaTranscodeManager_cancelTranscodingRequest }, +}; + +} // namespace anonymous + +int register_android_media_MediaTranscodeManager(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaTranscodeManager", gMethods, NELEM(gMethods)); +} diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp index f0fbc509cc9d..ecbe2b3eb8b7 100644 --- a/media/tests/MediaFrameworkTest/Android.bp +++ b/media/tests/MediaFrameworkTest/Android.bp @@ -7,6 +7,7 @@ android_test { ], static_libs: [ "mockito-target-minus-junit4", + "androidx.test.ext.junit", "androidx.test.rules", "android-ex-camera2", ], diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java new file mode 100644 index 000000000000..eeda50e5c095 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaframeworktest.functional.mediatranscodemanager; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.media.MediaTranscodeManager; +import android.util.Log; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class MediaTranscodeManagerTest { + private static final String TAG = "MediaTranscodeManagerTest"; + + /** The time to wait for the transcode operation to complete before failing the test. */ + private static final int TRANSCODE_TIMEOUT_SECONDS = 2; + + @Test + public void testMediaTranscodeManager() throws InterruptedException { + Log.d(TAG, "Starting: testMediaTranscodeManager"); + + Semaphore transcodeCompleteSemaphore = new Semaphore(0); + MediaTranscodeManager.TranscodingRequest request = + new MediaTranscodeManager.TranscodingRequest.Builder().build(); + Executor listenerExecutor = Executors.newSingleThreadExecutor(); + + MediaTranscodeManager mediaTranscodeManager = + MediaTranscodeManager.getInstance(ApplicationProvider.getApplicationContext()); + assertNotNull(mediaTranscodeManager); + + MediaTranscodeManager.TranscodingJob job; + job = mediaTranscodeManager.enqueueTranscodingRequest(request, listenerExecutor, + transcodingJob -> { + Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult()); + transcodeCompleteSemaphore.release(); + }); + assertNotNull(job); + + job.setOnProgressChangedListener( + listenerExecutor, progress -> Log.d(TAG, "Progress: " + progress)); + + if (job != null) { + Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete."); + boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire( + TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue("Transcode failed to complete in time.", finishedOnTime); + } + } +} diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index 8c0273b06e8c..ed93112d5288 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -16,14 +16,13 @@ package com.android.mediarouteprovider.example; -import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER; -import static android.media.MediaRoute2Info.DEVICE_TYPE_TV; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; import android.content.Intent; import android.media.MediaRoute2Info; -import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.IBinder; import android.text.TextUtils; @@ -46,8 +45,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to"; public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to"; - public static final String ROUTE_ID_SPECIAL_TYPE = "route_special_type"; - public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route"; + public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature"; + public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route"; public static final int VOLUME_MAX = 100; public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume"; @@ -58,49 +57,49 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; - public static final String TYPE_SAMPLE = - "com.android.mediarouteprovider.TYPE_SAMPLE"; - public static final String TYPE_SPECIAL = - "com.android.mediarouteprovider.TYPE_SPECIAL"; + public static final String FEATURE_SAMPLE = + "com.android.mediarouteprovider.FEATURE_SAMPLE"; + public static final String FEATURE_SPECIAL = + "com.android.mediarouteprovider.FEATURE_SPECIAL"; Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); - Map<String, String> mRouteSessionMap = new HashMap<>(); + Map<String, String> mRouteIdToSessionId = new HashMap<>(); private int mNextSessionId = 1000; private void initializeRoutes() { MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1) - .addRouteType(TYPE_SAMPLE) - .setDeviceType(DEVICE_TYPE_TV) + .addFeature(FEATURE_SAMPLE) + .setDeviceType(DEVICE_TYPE_REMOTE_TV) .build(); MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2) - .addRouteType(TYPE_SAMPLE) - .setDeviceType(DEVICE_TYPE_SPEAKER) + .addFeature(FEATURE_SAMPLE) + .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER) .build(); MediaRoute2Info route3 = new MediaRoute2Info.Builder( ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .build(); MediaRoute2Info route4 = new MediaRoute2Info.Builder( ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .build(); MediaRoute2Info route5 = new MediaRoute2Info.Builder( ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .build(); MediaRoute2Info routeSpecial = - new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_TYPE, ROUTE_NAME_SPECIAL_TYPE) - .addRouteType(TYPE_SAMPLE) - .addRouteType(TYPE_SPECIAL) + new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE) + .addFeature(FEATURE_SAMPLE) + .addFeature(FEATURE_SPECIAL) .build(); MediaRoute2Info fixedVolumeRoute = new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED) .build(); MediaRoute2Info variableVolumeRoute = new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(VOLUME_MAX) .build(); @@ -154,6 +153,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService @Override public void onUpdateVolume(String routeId, int delta) { + android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta); MediaRoute2Info route = mRoutes.get(routeId); if (route == null) { return; @@ -167,12 +167,12 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onCreateSession(String packageName, String routeId, String routeType, + public void onCreateSession(String packageName, String routeId, String routeFeature, long requestId) { MediaRoute2Info route = mRoutes.get(routeId); if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) { // Tell the router that session cannot be created by passing null as sessionInfo. - notifySessionCreated(/* sessionInfo= */ null, requestId); + notifySessionCreationFailed(requestId); return; } maybeDeselectRoute(routeId); @@ -183,10 +183,10 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService mRoutes.put(routeId, new MediaRoute2Info.Builder(route) .setClientPackageName(packageName) .build()); - mRouteSessionMap.put(routeId, sessionId); + mRouteIdToSessionId.put(routeId, sessionId); - RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder( - sessionId, packageName, routeType) + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + sessionId, packageName, routeFeature) .addSelectedRoute(routeId) .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT) .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO) @@ -196,9 +196,14 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) { - for (String routeId : lastSessionInfo.getSelectedRoutes()) { - mRouteSessionMap.remove(routeId); + public void onReleaseSession(String sessionId) { + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); + if (sessionInfo == null) { + return; + } + + for (String routeId : sessionInfo.getSelectedRoutes()) { + mRouteIdToSessionId.remove(routeId); MediaRoute2Info route = mRoutes.get(routeId); if (route != null) { mRoutes.put(routeId, new MediaRoute2Info.Builder(route) @@ -206,11 +211,12 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .build()); } } + notifySessionReleased(sessionId); } @Override public void onSelectRoute(String sessionId, String routeId) { - RouteSessionInfo sessionInfo = getSessionInfo(sessionId); + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); if (route == null || sessionInfo == null) { return; @@ -220,65 +226,66 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService mRoutes.put(routeId, new MediaRoute2Info.Builder(route) .setClientPackageName(sessionInfo.getClientPackageName()) .build()); - mRouteSessionMap.put(routeId, sessionId); + mRouteIdToSessionId.put(routeId, sessionId); - RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .addSelectedRoute(routeId) .removeSelectableRoute(routeId) .addDeselectableRoute(routeId) .build(); - updateSessionInfo(newSessionInfo); - notifySessionInfoChanged(newSessionInfo); + notifySessionUpdated(newSessionInfo); } @Override public void onDeselectRoute(String sessionId, String routeId) { - RouteSessionInfo sessionInfo = getSessionInfo(sessionId); + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); - mRouteSessionMap.remove(routeId); - if (sessionInfo == null || route == null) { + if (sessionInfo == null || route == null + || !sessionInfo.getSelectedRoutes().contains(routeId)) { return; } + + mRouteIdToSessionId.remove(routeId); mRoutes.put(routeId, new MediaRoute2Info.Builder(route) .setClientPackageName(null) .build()); - RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) + if (sessionInfo.getSelectedRoutes().size() == 1) { + notifySessionReleased(sessionId); + return; + } + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .removeSelectedRoute(routeId) .addSelectableRoute(routeId) .removeDeselectableRoute(routeId) .build(); - updateSessionInfo(newSessionInfo); - notifySessionInfoChanged(newSessionInfo); + notifySessionUpdated(newSessionInfo); } @Override public void onTransferToRoute(String sessionId, String routeId) { - RouteSessionInfo sessionInfo = getSessionInfo(sessionId); - RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .clearSelectedRoutes() .addSelectedRoute(routeId) .removeDeselectableRoute(routeId) .removeTransferrableRoute(routeId) .build(); - updateSessionInfo(newSessionInfo); - notifySessionInfoChanged(newSessionInfo); + notifySessionUpdated(newSessionInfo); } void maybeDeselectRoute(String routeId) { - if (!mRouteSessionMap.containsKey(routeId)) { + if (!mRouteIdToSessionId.containsKey(routeId)) { return; } - String sessionId = mRouteSessionMap.get(routeId); + String sessionId = mRouteIdToSessionId.get(routeId); onDeselectRoute(sessionId, routeId); } void publishRoutes() { - MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder() - .addRoutes(mRoutes.values()) - .build(); - updateProviderInfo(info); + notifyRoutes(mRoutes.values()); } } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java index ce4bb8ef2688..007229a4df2b 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java @@ -18,23 +18,23 @@ package com.android.mediaroutertest; import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED; import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING; -import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER; -import static android.media.MediaRoute2Info.DEVICE_TYPE_TV; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_ALL; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SPECIAL; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO; -import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_TYPE; +import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_FEATURE; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME; import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_ALL; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_SPECIAL; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SAMPLE; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SPECIAL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -48,10 +48,10 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2; import android.media.MediaRouter2.RouteCallback; -import android.media.MediaRouter2.RouteSessionController; +import android.media.MediaRouter2.RoutingController; import android.media.MediaRouter2.SessionCallback; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; import android.net.Uri; import android.os.Parcel; import android.support.test.InstrumentationRegistry; @@ -100,10 +100,10 @@ public class MediaRouter2Test { */ @Test public void testGetRoutes() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_SPECIAL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL); assertEquals(1, routes.size()); - assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE)); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE)); } @Test @@ -115,9 +115,9 @@ public class MediaRouter2Test { .setIconUri(new Uri.Builder().path("icon").build()) .setVolume(5) .setVolumeMax(20) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) - .setDeviceType(DEVICE_TYPE_SPEAKER) + .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER) .build(); MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build(); @@ -138,21 +138,13 @@ public class MediaRouter2Test { .setClientPackageName("com.android.mediaroutertest") .setConnectionState(CONNECTION_STATE_CONNECTING) .setIconUri(new Uri.Builder().path("icon").build()) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolume(5) .setVolumeMax(20) .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) - .setDeviceType(DEVICE_TYPE_SPEAKER) + .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER) .build(); - MediaRoute2Info routeId = new MediaRoute2Info.Builder(route) - .setId("another id").build(); - assertNotEquals(route, routeId); - - MediaRoute2Info routeName = new MediaRoute2Info.Builder(route) - .setName("another name").build(); - assertNotEquals(route, routeName); - MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route) .setDescription("another description").build(); assertNotEquals(route, routeDescription); @@ -170,7 +162,7 @@ public class MediaRouter2Test { assertNotEquals(route, routeClient); MediaRoute2Info routeType = new MediaRoute2Info.Builder(route) - .addRouteType(TYPE_SPECIAL).build(); + .addFeature(FEATURE_SPECIAL).build(); assertNotEquals(route, routeType); MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route) @@ -186,13 +178,13 @@ public class MediaRouter2Test { assertNotEquals(route, routeVolumeHandling); MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route) - .setVolume(DEVICE_TYPE_TV).build(); + .setVolume(DEVICE_TYPE_REMOTE_TV).build(); assertNotEquals(route, routeDeviceType); } @Test public void testControlVolumeWithRouter() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); assertNotNull(volRoute); @@ -204,13 +196,13 @@ public class MediaRouter2Test { () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == originalVolume + deltaVolume), - TYPES_ALL); + FEATURES_ALL); awaitOnRouteChanged( () -> mRouter2.requestSetVolume(volRoute, originalVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == originalVolume), - TYPES_ALL); + FEATURES_ALL); } @Test @@ -237,13 +229,13 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionWithInvalidArguments() { MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); - String routeType = "routeType"; + String routeFeature = "routeFeature"; // Tests null route assertThrows(NullPointerException.class, - () -> mRouter2.requestCreateSession(null, routeType)); + () -> mRouter2.requestCreateSession(null, routeFeature)); - // Tests null or empty route type + // Tests null or empty route feature assertThrows(IllegalArgumentException.class, () -> mRouter2.requestCreateSession(route, null)); assertThrows(IllegalArgumentException.class, @@ -252,42 +244,42 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionSuccess() throws Exception { - final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + final List<String> sampleRouteFeature = new ArrayList<>(); + sampleRouteFeature.add(FEATURE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature); MediaRoute2Info route = routes.get(ROUTE_ID1); assertNotNull(route); final CountDownLatch successLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, TYPE_SAMPLE); + mRouter2.requestCreateSession(route, FEATURE_SAMPLE); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -302,7 +294,7 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionFailure() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED); @@ -310,32 +302,32 @@ public class MediaRouter2Test { final CountDownLatch successLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { controllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { assertEquals(route, requestedRoute); - assertTrue(TextUtils.equals(TYPE_SAMPLE, requestedRouteType)); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, requestedRouteFeature)); failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, TYPE_SAMPLE); + mRouter2.requestCreateSession(route, FEATURE_SAMPLE); assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreated should not be called. @@ -350,23 +342,23 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionMultipleSessions() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); final CountDownLatch successLatch = new CountDownLatch(2); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> createdControllers = new ArrayList<>(); + final List<RoutingController> createdControllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { createdControllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { failureLatch.countDown(); } }; @@ -379,12 +371,12 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route1, TYPE_SAMPLE); - mRouter2.requestCreateSession(route2, TYPE_SAMPLE); + mRouter2.requestCreateSession(route1, FEATURE_SAMPLE); + mRouter2.requestCreateSession(route2, FEATURE_SAMPLE); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -392,14 +384,14 @@ public class MediaRouter2Test { // Created controllers should have proper info assertEquals(2, createdControllers.size()); - RouteSessionController controller1 = createdControllers.get(0); - RouteSessionController controller2 = createdControllers.get(1); + RoutingController controller1 = createdControllers.get(0); + RoutingController controller2 = createdControllers.get(1); assertNotEquals(controller1.getSessionId(), controller2.getSessionId()); assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1)); assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType())); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature())); } finally { releaseControllers(createdControllers); @@ -411,7 +403,7 @@ public class MediaRouter2Test { @Test public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route = routes.get(ROUTE_ID1); @@ -419,30 +411,30 @@ public class MediaRouter2Test { final CountDownLatch successLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { controllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, TYPE_SAMPLE); + mRouter2.requestCreateSession(route, FEATURE_SAMPLE); // Unregisters session callback mRouter2.unregisterSessionCallback(sessionCallback); @@ -459,9 +451,9 @@ public class MediaRouter2Test { // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route) @Test - public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception { + public void testRoutingControllerSelectAndDeselectRoute() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); @@ -470,22 +462,22 @@ public class MediaRouter2Test { final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with ROUTE_ID1 SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @Override - public void onSessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + public void onSessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 || !TextUtils.equals( controllers.get(0).getSessionId(), controller.getSessionId())) { @@ -527,15 +519,15 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); - RouteSessionController controller = controllers.get(0); + RoutingController controller = controllers.get(0); assertTrue(getRouteIds(controller.getSelectableRoutes()) .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT)); @@ -558,9 +550,9 @@ public class MediaRouter2Test { } @Test - public void testRouteSessionControllerTransferToRoute() throws Exception { + public void testRoutingControllerTransferToRoute() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); @@ -568,26 +560,22 @@ public class MediaRouter2Test { final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with ROUTE_ID1 SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); - android.util.Log.d(TAG, "selected route ids "); - for (String routeId : getRouteIds(controller.getSelectedRoutes())) { - android.util.Log.d(TAG, "route id : " + routeId); - } assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @Override - public void onSessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + public void onSessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 || !TextUtils.equals( controllers.get(0).getSessionId(), controller.getSessionId())) { @@ -613,15 +601,15 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); - RouteSessionController controller = controllers.get(0); + RoutingController controller = controllers.get(0); assertTrue(getRouteIds(controller.getTransferrableRoutes()) .contains(ROUTE_ID5_TO_TRANSFER_TO)); @@ -641,9 +629,9 @@ public class MediaRouter2Test { // TODO: Add tests for onSessionReleased() call. @Test - public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception { + public void testRoutingControllerReleaseShouldIgnoreTransferTo() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); @@ -651,22 +639,22 @@ public class MediaRouter2Test { final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with ROUTE_ID1 SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @Override - public void onSessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + public void onSessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 || !TextUtils.equals( controllers.get(0).getSessionId(), controller.getSessionId())) { @@ -678,15 +666,15 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); - RouteSessionController controller = controllers.get(0); + RoutingController controller = controllers.get(0); assertTrue(getRouteIds(controller.getTransferrableRoutes()) .contains(ROUTE_ID5_TO_TRANSFER_TO)); @@ -735,7 +723,7 @@ public class MediaRouter2Test { }; mRouter2.registerRouteCallback(mExecutor, routeCallback, - new RouteDiscoveryRequest.Builder(routeTypes, true).build()); + new RouteDiscoveryPreference.Builder(routeTypes, true).build()); try { latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); return createRouteMap(mRouter2.getRoutes()); @@ -744,8 +732,8 @@ public class MediaRouter2Test { } } - static void releaseControllers(@NonNull List<RouteSessionController> controllers) { - for (RouteSessionController controller : controllers) { + static void releaseControllers(@NonNull List<RoutingController> controllers) { + for (RoutingController controller : controllers) { controller.release(); } } @@ -775,7 +763,7 @@ public class MediaRouter2Test { } }; mRouter2.registerRouteCallback(mExecutor, routeCallback, - new RouteDiscoveryRequest.Builder(routeTypes, true).build()); + new RouteDiscoveryPreference.Builder(routeTypes, true).build()); try { task.run(); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 9ff9177c1b40..ae15b913631b 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -31,7 +31,7 @@ import android.media.MediaRouter2; import android.media.MediaRouter2.RouteCallback; import android.media.MediaRouter2.SessionCallback; import android.media.MediaRouter2Manager; -import android.media.RouteDiscoveryRequest; +import android.media.RouteDiscoveryPreference; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -76,9 +76,9 @@ public class MediaRouterManagerTest { SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to"; public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to"; - public static final String ROUTE_ID_SPECIAL_TYPE = - SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_type"; - public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route"; + public static final String ROUTE_ID_SPECIAL_FEATURE = + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_feature"; + public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route"; public static final String SYSTEM_PROVIDER_ID = "com.android.server.media/.SystemMediaRoute2Provider"; @@ -94,12 +94,12 @@ public class MediaRouterManagerTest { public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; - public static final String TYPE_SAMPLE = - "com.android.mediarouteprovider.TYPE_SAMPLE"; - public static final String TYPE_SPECIAL = - "com.android.mediarouteprovider.TYPE_SPECIAL"; + public static final String FEATURE_SAMPLE = + "com.android.mediarouteprovider.FEATURE_SAMPLE"; + public static final String FEATURE_SPECIAL = + "com.android.mediarouteprovider.FEATURE_SPECIAL"; - private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; + private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO"; private static final int TIMEOUT_MS = 5000; @@ -113,18 +113,18 @@ public class MediaRouterManagerTest { private final List<RouteCallback> mRouteCallbacks = new ArrayList<>(); private final List<SessionCallback> mSessionCallbacks = new ArrayList<>(); - public static final List<String> TYPES_ALL = new ArrayList(); - public static final List<String> TYPES_SPECIAL = new ArrayList(); - private static final List<String> TYPES_LIVE_AUDIO = new ArrayList<>(); + public static final List<String> FEATURES_ALL = new ArrayList(); + public static final List<String> FEATURES_SPECIAL = new ArrayList(); + private static final List<String> FEATURES_LIVE_AUDIO = new ArrayList<>(); static { - TYPES_ALL.add(TYPE_SAMPLE); - TYPES_ALL.add(TYPE_SPECIAL); - TYPES_ALL.add(TYPE_LIVE_AUDIO); + FEATURES_ALL.add(FEATURE_SAMPLE); + FEATURES_ALL.add(FEATURE_SPECIAL); + FEATURES_ALL.add(FEATURE_LIVE_AUDIO); - TYPES_SPECIAL.add(TYPE_SPECIAL); + FEATURES_SPECIAL.add(FEATURE_SPECIAL); - TYPES_LIVE_AUDIO.add(TYPE_LIVE_AUDIO); + FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO); } @Before @@ -181,7 +181,7 @@ public class MediaRouterManagerTest { @Test public void testOnRoutesRemoved() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @@ -203,14 +203,14 @@ public class MediaRouterManagerTest { } /** - * Tests if we get proper routes for application that has special route type. + * Tests if we get proper routes for application that has special route feature. */ @Test - public void testRouteType() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_SPECIAL); + public void testRouteFeatures() throws Exception { + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL); assertEquals(1, routes.size()); - assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE)); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE)); } /** @@ -219,7 +219,7 @@ public class MediaRouterManagerTest { */ @Test public void testRouterOnSessionCreated() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); CountDownLatch latch = new CountDownLatch(1); @@ -228,7 +228,7 @@ public class MediaRouterManagerTest { addRouterCallback(new MediaRouter2.RouteCallback()); addSessionCallback(new SessionCallback() { @Override - public void onSessionCreated(MediaRouter2.RouteSessionController controller) { + public void onSessionCreated(MediaRouter2.RoutingController controller) { if (createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) { latch.countDown(); } @@ -255,7 +255,7 @@ public class MediaRouterManagerTest { @Ignore("TODO: test session created callback instead of onRouteSelected") public void testManagerOnRouteSelected() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @@ -285,7 +285,7 @@ public class MediaRouterManagerTest { public void testGetActiveRoutes() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @Override @@ -321,7 +321,7 @@ public class MediaRouterManagerTest { @Test @Ignore("TODO: enable when session is released") public void testSingleProviderSelect() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); awaitOnRouteChangedManager( @@ -346,7 +346,7 @@ public class MediaRouterManagerTest { @Test public void testControlVolumeWithManager() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); @@ -365,7 +365,7 @@ public class MediaRouterManagerTest { @Test public void testVolumeHandling() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME); MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); @@ -375,11 +375,11 @@ public class MediaRouterManagerTest { assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax()); } - Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeTypes) + Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures) throws Exception { CountDownLatch latch = new CountDownLatch(2); - // A dummy callback is required to send route type info. + // A dummy callback is required to send route feature info. RouteCallback routeCallback = new RouteCallback(); MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() { @Override @@ -394,16 +394,17 @@ public class MediaRouterManagerTest { } @Override - public void onControlCategoriesChanged(String packageName, List<String> routeTypes) { + public void onControlCategoriesChanged(String packageName, + List<String> preferredFeatures) { if (TextUtils.equals(mPackageName, packageName) - && routeTypes.equals(routeTypes)) { + && preferredFeatures.equals(preferredFeatures)) { latch.countDown(); } } }; mManager.registerCallback(mExecutor, managerCallback); mRouter2.registerRouteCallback(mExecutor, routeCallback, - new RouteDiscoveryRequest.Builder(routeTypes, true).build()); + new RouteDiscoveryPreference.Builder(routeFeatures, true).build()); try { latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); return createRouteMap(mManager.getAvailableRoutes(mPackageName)); @@ -450,7 +451,7 @@ public class MediaRouterManagerTest { private void addRouterCallback(RouteCallback routeCallback) { mRouteCallbacks.add(routeCallback); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); } private void addSessionCallback(SessionCallback sessionCallback) { diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java index 60d131a8fb7e..fa129350ed8b 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java @@ -19,7 +19,7 @@ package com.android.mediaroutertest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import android.media.RouteDiscoveryRequest; +import android.media.RouteDiscoveryPreference; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -34,7 +34,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest -public class RouteDiscoveryRequestTest { +public class RouteDiscoveryPreferenceTest { @Before public void setUp() throws Exception { } @@ -46,10 +46,10 @@ public class RouteDiscoveryRequestTest { List<String> testTypes = new ArrayList<>(); testTypes.add("TEST_TYPE_1"); testTypes.add("TEST_TYPE_2"); - RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true) + RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true) .build(); - RouteDiscoveryRequest requestRebuilt = new RouteDiscoveryRequest.Builder(request) + RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request) .build(); assertEquals(request, requestRebuilt); @@ -57,7 +57,7 @@ public class RouteDiscoveryRequestTest { Parcel parcel = Parcel.obtain(); parcel.writeParcelable(request, 0); parcel.setDataPosition(0); - RouteDiscoveryRequest requestFromParcel = parcel.readParcelable(null); + RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null); assertEquals(request, requestFromParcel); } @@ -71,15 +71,15 @@ public class RouteDiscoveryRequestTest { List<String> testTypes2 = new ArrayList<>(); testTypes.add("TEST_TYPE_3"); - RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true) + RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true) .build(); - RouteDiscoveryRequest requestTypes = new RouteDiscoveryRequest.Builder(request) - .setRouteTypes(testTypes2) + RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request) + .setPreferredFeatures(testTypes2) .build(); assertNotEquals(request, requestTypes); - RouteDiscoveryRequest requestActiveScan = new RouteDiscoveryRequest.Builder(request) + RouteDiscoveryPreference requestActiveScan = new RouteDiscoveryPreference.Builder(request) .setActiveScan(false) .build(); assertNotEquals(request, requestActiveScan); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java deleted file mode 100644 index 9971fc3bbe9f..000000000000 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.mediaroutertest; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.media.RouteSessionInfo; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class RouteSessionTest { - private static final String TEST_SESSION_ID = "test_session_id"; - private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest"; - private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category"; - - private static final String TEST_ROUTE_ID1 = "route_id1"; - - @Test - public void testValidity() { - RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, - "", - TEST_CONTROL_CATEGORY) - .addSelectedRoute(TEST_ROUTE_ID1) - .build(); - RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID, - TEST_PACKAGE_NAME, "") - .addSelectedRoute(TEST_ROUTE_ID1) - .build(); - - RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, - TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY) - .build(); - - RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession) - .addSelectedRoute(TEST_ROUTE_ID1) - .build(); - - assertFalse(emptyPackageSession.isValid()); - assertFalse(emptyCategorySession.isValid()); - assertFalse(emptySelectedRouteSession.isValid()); - assertTrue(validSession.isValid()); - } -} diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java new file mode 100644 index 000000000000..3f5973615e32 --- /dev/null +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java @@ -0,0 +1,560 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaroutertest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertThrows; + +import android.media.RoutingSessionInfo; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RoutingSessionInfoTest { + public static final String TEST_ID = "test_id"; + public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name"; + public static final String TEST_ROUTE_FEATURE = "test_route_feature"; + + public static final String TEST_ROUTE_ID_0 = "test_route_type_0"; + public static final String TEST_ROUTE_ID_1 = "test_route_type_1"; + public static final String TEST_ROUTE_ID_2 = "test_route_type_2"; + public static final String TEST_ROUTE_ID_3 = "test_route_type_3"; + public static final String TEST_ROUTE_ID_4 = "test_route_type_4"; + public static final String TEST_ROUTE_ID_5 = "test_route_type_5"; + public static final String TEST_ROUTE_ID_6 = "test_route_type_6"; + public static final String TEST_ROUTE_ID_7 = "test_route_type_7"; + + public static final String TEST_KEY = "test_key"; + public static final String TEST_VALUE = "test_value"; + + @Test + public void testBuilderConstructorWithInvalidValues() { + final String nullId = null; + final String nullClientPackageName = null; + final String nullRouteFeature = null; + + final String emptyId = ""; + // Note: An empty string as client package name is valid. + final String emptyRouteFeature = ""; + + final String validId = TEST_ID; + final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME; + final String validRouteFeature = TEST_ROUTE_FEATURE; + + // ID is invalid + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, validClientPackageName, validRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, validClientPackageName, validRouteFeature)); + + // client package name is invalid (null) + assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( + validId, nullClientPackageName, validRouteFeature)); + + // route feature is invalid + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + validId, validClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + validId, validClientPackageName, emptyRouteFeature)); + + // Two arguments are invalid - (1) ID and clientPackageName + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, nullClientPackageName, validRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, nullClientPackageName, validRouteFeature)); + + // Two arguments are invalid - (2) ID and routeFeature + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, validClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, validClientPackageName, emptyRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, validClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, validClientPackageName, emptyRouteFeature)); + + // Two arguments are invalid - (3) clientPackageName and routeFeature + // Note that this throws NullPointerException. + assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( + validId, nullClientPackageName, nullRouteFeature)); + assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( + validId, nullClientPackageName, emptyRouteFeature)); + + // All arguments are invalid + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, nullClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, nullClientPackageName, emptyRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, nullClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, nullClientPackageName, emptyRouteFeature)); + + // Null RouteInfo (1-argument constructor) + final RoutingSessionInfo nullRoutingSessionInfo = null; + assertThrows(NullPointerException.class, + () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo)); + } + + @Test + public void testBuilderConstructorWithEmptyClientPackageName() { + // An empty string for client package name is valid. (for unknown cases) + // Creating builder with it should not throw any exception. + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, "" /* clientPackageName*/, TEST_ROUTE_FEATURE); + } + + @Test + public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() { + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + // Note: Calling build() without adding any selected routes. + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() { + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + + final String nullRouteId = null; + final String emptyRouteId = ""; + + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectedRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addDeselectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addTransferrableRoute(nullRouteId)); + + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectedRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addDeselectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addTransferrableRoute(emptyRouteId)); + } + + @Test + public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() { + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + + final String nullRouteId = null; + final String emptyRouteId = ""; + + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectedRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeDeselectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeTransferrableRoute(nullRouteId)); + + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectedRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeDeselectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeTransferrableRoute(emptyRouteId)); + } + + @Test + public void testBuilderAndGettersOfRoutingSessionInfo() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + assertEquals(TEST_ID, sessionInfo.getId()); + assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName()); + assertEquals(TEST_ROUTE_FEATURE, sessionInfo.getRouteFeature()); + + assertEquals(2, sessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1)); + + assertEquals(2, sessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1)); + + assertEquals(2, sessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1)); + + assertEquals(2, sessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferrableRoutes().get(1)); + + Bundle controlHintsOut = sessionInfo.getControlHints(); + assertNotNull(controlHintsOut); + assertTrue(controlHintsOut.containsKey(TEST_KEY)); + assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY)); + } + + @Test + public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .build(); + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + assertEquals(2, newSessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1)); + + assertEquals(2, newSessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1)); + + assertEquals(2, newSessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1)); + + assertEquals(2, newSessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferrableRoutes().get(1)); + } + + @Test + public void testBuilderRemoveRouteMethods() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .removeSelectedRoute(TEST_ROUTE_ID_1) + + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .removeSelectableRoute(TEST_ROUTE_ID_3) + + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .removeDeselectableRoute(TEST_ROUTE_ID_5) + + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .removeTransferrableRoute(TEST_ROUTE_ID_7) + + .build(); + + assertEquals(1, sessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0)); + + assertEquals(1, sessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0)); + + assertEquals(1, sessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0)); + + assertEquals(1, sessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0)); + } + + @Test + public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .removeSelectedRoute(TEST_ROUTE_ID_1) + .removeSelectableRoute(TEST_ROUTE_ID_3) + .removeDeselectableRoute(TEST_ROUTE_ID_5) + .removeTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + assertEquals(1, newSessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0)); + + assertEquals(1, newSessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0)); + + assertEquals(1, newSessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0)); + + assertEquals(1, newSessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0)); + } + + @Test + public void testBuilderClearRouteMethods() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .clearSelectedRoutes() + + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .clearSelectableRoutes() + + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .clearDeselectableRoutes() + + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .clearTransferrableRoutes() + + // SelectedRoutes must not be empty + .addSelectedRoute(TEST_ROUTE_ID_0) + .build(); + + assertEquals(1, sessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0)); + + assertTrue(sessionInfo.getSelectableRoutes().isEmpty()); + assertTrue(sessionInfo.getDeselectableRoutes().isEmpty()); + assertTrue(sessionInfo.getTransferrableRoutes().isEmpty()); + } + + @Test + public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .clearSelectedRoutes() + .clearSelectableRoutes() + .clearDeselectableRoutes() + .clearTransferrableRoutes() + // SelectedRoutes must not be empty + .addSelectedRoute(TEST_ROUTE_ID_0) + .build(); + + assertEquals(1, newSessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0)); + + assertTrue(newSessionInfo.getSelectableRoutes().isEmpty()); + assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty()); + assertTrue(newSessionInfo.getTransferrableRoutes().isEmpty()); + } + + @Test + public void testEqualsCreatedWithSameArguments() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + assertEquals(sessionInfo1, sessionInfo2); + assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode()); + } + + @Test + public void testEqualsCreatedWithBuilderCopyConstructor() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build(); + + assertEquals(sessionInfo1, sessionInfo2); + assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode()); + } + + @Test + public void testEqualsReturnFalse() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + // Now, we will use copy constructor + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addSelectedRoute("randomRoute") + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addSelectableRoute("randomRoute") + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addDeselectableRoute("randomRoute") + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addTransferrableRoute("randomRoute") + .build()); + + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeSelectedRoute(TEST_ROUTE_ID_1) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeSelectableRoute(TEST_ROUTE_ID_3) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeDeselectableRoute(TEST_ROUTE_ID_5) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeTransferrableRoute(TEST_ROUTE_ID_7) + .build()); + + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearSelectedRoutes() + // Note: Calling build() with empty selected routes will throw IAE. + .addSelectedRoute(TEST_ROUTE_ID_0) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearSelectableRoutes() + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearDeselectableRoutes() + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearTransferrableRoutes() + .build()); + + // Note: ControlHints will not affect the equals. + } + + @Test + public void testParcelingAndUnParceling() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + Parcel parcel = Parcel.obtain(); + sessionInfo.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + RoutingSessionInfo sessionInfoFromParcel = + RoutingSessionInfo.CREATOR.createFromParcel(parcel); + assertEquals(sessionInfo, sessionInfoFromParcel); + assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode()); + + // Check controlHints + Bundle controlHintsOut = sessionInfoFromParcel.getControlHints(); + assertNotNull(controlHintsOut); + assertTrue(controlHintsOut.containsKey(TEST_KEY)); + assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY)); + } +} diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types index cb04d921bd67..c1f8b3f0e195 100644 --- a/mime/java-res/android.mime.types +++ b/mime/java-res/android.mime.types @@ -46,6 +46,7 @@ # <extension1> and <extension2> are mapped (or remapped) to <mimeType>. ?application/epub+zip epub +?application/lrc lrc ?application/pkix-cert cer ?application/rss+xml rss ?application/sdp sdp @@ -65,6 +66,7 @@ ?application/x-mpegurl m3u m3u8 ?application/x-pem-file pem ?application/x-pkcs12 p12 pfx +?application/x-subrip srt ?application/x-webarchive webarchive ?application/x-webarchive-xml webarchivexml ?application/x-x509-server-cert crt diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java index 24ea3cfc5a75..cf55eba6e5ba 100644 --- a/mms/java/android/telephony/MmsManager.java +++ b/mms/java/android/telephony/MmsManager.java @@ -18,6 +18,7 @@ package android.telephony; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemService; import android.app.ActivityThread; import android.app.PendingIntent; import android.content.Context; @@ -30,8 +31,10 @@ import com.android.internal.telephony.IMms; /** * Manages MMS operations such as sending multimedia messages. + * Get this object by calling Context#getSystemService(Context#MMS_SERVICE). */ -public final class MmsManager { +@SystemService(Context.MMS_SERVICE) +public class MmsManager { private static final String TAG = "MmsManager"; private final Context mContext; diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 942eafdbc48d..376ea77740c2 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -24,12 +24,21 @@ cc_library_shared { // our source files // - srcs: ["bitmap.cpp"], + srcs: [ + "aassetstreamadaptor.cpp", + "bitmap.cpp", + "imagedecoder.cpp", + ], shared_libs: [ + "libandroid", "libandroid_runtime", + "libhwui", + "liblog", ], + static_libs: ["libarect"], + arch: { arm: { // TODO: This is to work around b/24465209. Remove after root cause is fixed diff --git a/native/graphics/jni/aassetstreamadaptor.cpp b/native/graphics/jni/aassetstreamadaptor.cpp new file mode 100644 index 000000000000..016008bfce00 --- /dev/null +++ b/native/graphics/jni/aassetstreamadaptor.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "aassetstreamadaptor.h" + +#include <log/log.h> + +AAssetStreamAdaptor::AAssetStreamAdaptor(AAsset* asset) + : mAAsset(asset) +{ +} + +bool AAssetStreamAdaptor::rewind() { + off64_t pos = AAsset_seek64(mAAsset, 0, SEEK_SET); + if (pos == (off64_t)-1) { + ALOGE("rewind failed!"); + return false; + } + return true; +} + +size_t AAssetStreamAdaptor::getLength() const { + return AAsset_getLength64(mAAsset); +} + +bool AAssetStreamAdaptor::isAtEnd() const { + return AAsset_getRemainingLength64(mAAsset) == 0; +} + +SkStreamRewindable* AAssetStreamAdaptor::onDuplicate() const { + // Cannot sensibly create a duplicate, since each AAssetStreamAdaptor + // would be modifying the same AAsset. + //return new AAssetStreamAdaptor(mAAsset); + return nullptr; +} + +bool AAssetStreamAdaptor::hasPosition() const { + return AAsset_seek64(mAAsset, 0, SEEK_CUR) != -1; +} + +size_t AAssetStreamAdaptor::getPosition() const { + const off64_t offset = AAsset_seek64(mAAsset, 0, SEEK_CUR); + if (offset == -1) { + ALOGE("getPosition failed!"); + return 0; + } + + return offset; +} + +bool AAssetStreamAdaptor::seek(size_t position) { + if (AAsset_seek64(mAAsset, position, SEEK_SET) == -1) { + ALOGE("seek failed!"); + return false; + } + + return true; +} + +bool AAssetStreamAdaptor::move(long offset) { + if (AAsset_seek64(mAAsset, offset, SEEK_CUR) == -1) { + ALOGE("move failed!"); + return false; + } + + return true; +} + +size_t AAssetStreamAdaptor::read(void* buffer, size_t size) { + ssize_t amount; + + if (!buffer) { + if (!size) { + return 0; + } + + // asset->seek returns new total offset + // we want to return amount that was skipped + const off64_t oldOffset = AAsset_seek64(mAAsset, 0, SEEK_CUR); + if (oldOffset == -1) { + ALOGE("seek(oldOffset) failed!"); + return 0; + } + + const off64_t newOffset = AAsset_seek64(mAAsset, size, SEEK_CUR); + if (-1 == newOffset) { + ALOGE("seek(%zu) failed!", size); + return 0; + } + amount = newOffset - oldOffset; + } else { + amount = AAsset_read(mAAsset, buffer, size); + } + + if (amount < 0) { + amount = 0; + } + return amount; +} + +const void* AAssetStreamAdaptor::getMemoryBase() { + return AAsset_getBuffer(mAAsset); +} + diff --git a/native/graphics/jni/aassetstreamadaptor.h b/native/graphics/jni/aassetstreamadaptor.h new file mode 100644 index 000000000000..c1fddb01344d --- /dev/null +++ b/native/graphics/jni/aassetstreamadaptor.h @@ -0,0 +1,50 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SkStream.h" + +#include <android/asset_manager.h> + +// Like AssetStreamAdaptor, but operates on AAsset, a public NDK API. +class AAssetStreamAdaptor : public SkStreamRewindable { +public: + /** + * Create an SkStream that reads from an AAsset. + * + * The AAsset must remain open for the lifetime of the Adaptor. The Adaptor + * does *not* close the AAsset. + */ + explicit AAssetStreamAdaptor(AAsset*); + + bool rewind() override; + size_t read(void* buffer, size_t size) override; + bool hasLength() const override { return true; } + size_t getLength() const override; + bool hasPosition() const override; + size_t getPosition() const override; + bool seek(size_t position) override; + bool move(long offset) override; + bool isAtEnd() const override; + const void* getMemoryBase() override; +protected: + SkStreamRewindable* onDuplicate() const override; + +private: + AAsset* mAAsset; +}; + diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp index 26c7f8d709e7..1aebeaf1e7e8 100644 --- a/native/graphics/jni/bitmap.cpp +++ b/native/graphics/jni/bitmap.cpp @@ -16,7 +16,6 @@ #include <android/bitmap.h> #include <android/graphics/bitmap.h> -#include <android/data_space.h> int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info) { @@ -30,15 +29,6 @@ int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, return ANDROID_BITMAP_RESULT_SUCCESS; } -int32_t AndroidBitmap_getDataSpace(JNIEnv* env, jobject jbitmap) { - if (NULL == env || NULL == jbitmap) { - return ADATASPACE_UNKNOWN; // Or return a real error? - } - - android::graphics::Bitmap bitmap(env, jbitmap); - return bitmap.getDataSpace(); -} - int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { if (NULL == env || NULL == jbitmap) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp new file mode 100644 index 000000000000..2ef203dd466f --- /dev/null +++ b/native/graphics/jni/imagedecoder.cpp @@ -0,0 +1,320 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "aassetstreamadaptor.h" + +#include <android/asset_manager.h> +#include <android/bitmap.h> +#include <android/imagedecoder.h> +#include <android/graphics/MimeType.h> +#include <android/rect.h> +#include <hwui/ImageDecoder.h> +#include <log/log.h> +#include <SkAndroidCodec.h> + +#include <fcntl.h> +#include <optional> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +using namespace android; + +int ResultToErrorCode(SkCodec::Result result) { + switch (result) { + case SkCodec::kIncompleteInput: + return ANDROID_IMAGE_DECODER_INCOMPLETE; + case SkCodec::kErrorInInput: + return ANDROID_IMAGE_DECODER_ERROR; + case SkCodec::kInvalidInput: + return ANDROID_IMAGE_DECODER_INVALID_INPUT; + case SkCodec::kCouldNotRewind: + return ANDROID_IMAGE_DECODER_SEEK_ERROR; + case SkCodec::kUnimplemented: + return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT; + case SkCodec::kInvalidConversion: + return ANDROID_IMAGE_DECODER_INVALID_CONVERSION; + case SkCodec::kInvalidParameters: + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + case SkCodec::kSuccess: + return ANDROID_IMAGE_DECODER_SUCCESS; + case SkCodec::kInvalidScale: + return ANDROID_IMAGE_DECODER_INVALID_SCALE; + case SkCodec::kInternalError: + return ANDROID_IMAGE_DECODER_INTERNAL_ERROR; + } +} + +static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) { + SkCodec::Result result; + auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr, + SkCodec::SelectionPolicy::kPreferAnimation); + auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), + SkAndroidCodec::ExifOrientationBehavior::kRespect); + if (!androidCodec) { + return ResultToErrorCode(result); + } + + *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec))); + return ANDROID_IMAGE_DECODER_SUCCESS; +} + +int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) { + if (!asset || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + *outDecoder = nullptr; + + auto stream = std::make_unique<AAssetStreamAdaptor>(asset); + return createFromStream(std::move(stream), outDecoder); +} + +static bool isSeekable(int descriptor) { + return ::lseek64(descriptor, 0, SEEK_CUR) != -1; +} + +int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) { + if (fd <= 0 || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + struct stat fdStat; + if (fstat(fd, &fdStat) == -1) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + if (!isSeekable(fd)) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + // SkFILEStream will close its descriptor. Duplicate it so the client will + // still be responsible for closing the original. + int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0); + FILE* file = fdopen(dupDescriptor, "r"); + if (!file) { + close(dupDescriptor); + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file)); + return createFromStream(std::move(stream), outDecoder); +} + +int AImageDecoder_createFromBuffer(const void* buffer, size_t length, + AImageDecoder** outDecoder) { + if (!buffer || !length || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + *outDecoder = nullptr; + + // The client is expected to keep the buffer alive as long as the + // AImageDecoder, so we do not need to copy the buffer. + auto stream = std::unique_ptr<SkStreamRewindable>( + new SkMemoryStream(buffer, length, false /* copyData */)); + return createFromStream(std::move(stream), outDecoder); +} + +static ImageDecoder* toDecoder(AImageDecoder* d) { + return reinterpret_cast<ImageDecoder*>(d); +} + +// Note: This differs from the version in android_bitmap.cpp in that this +// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec +// allows decoding single channel images to gray, which Android then treats +// as A_8/ALPHA_8. +static SkColorType getColorType(AndroidBitmapFormat format) { + switch (format) { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + return kN32_SkColorType; + case ANDROID_BITMAP_FORMAT_RGB_565: + return kRGB_565_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_4444: + return kARGB_4444_SkColorType; + case ANDROID_BITMAP_FORMAT_A_8: + return kGray_8_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_F16: + return kRGBA_F16_SkColorType; + default: + return kUnknown_SkColorType; + } +} + +int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) { + if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE + || format > ANDROID_BITMAP_FORMAT_RGBA_F16) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format)) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; +} + +const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) { + return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder); +} + +static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) { + return reinterpret_cast<const ImageDecoder*>(info); +} + +int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) { + if (!info) { + return 0; + } + return toDecoder(info)->mCodec->getInfo().width(); +} + +int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) { + if (!info) { + return 0; + } + return toDecoder(info)->mCodec->getInfo().height(); +} + +const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) { + if (!info) { + return nullptr; + } + return getMimeType(toDecoder(info)->mCodec->getEncodedFormat()); +} + +bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) { + if (!info) { + return false; + } + return toDecoder(info)->mCodec->codec()->getFrameCount() > 1; +} + +// FIXME: Share with getFormat in android_bitmap.cpp? +static AndroidBitmapFormat getFormat(SkColorType colorType) { + switch (colorType) { + case kN32_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_8888; + case kRGB_565_SkColorType: + return ANDROID_BITMAP_FORMAT_RGB_565; + case kARGB_4444_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_4444; + case kAlpha_8_SkColorType: + return ANDROID_BITMAP_FORMAT_A_8; + case kRGBA_F16_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_F16; + default: + return ANDROID_BITMAP_FORMAT_NONE; + } +} + +AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat( + const AImageDecoderHeaderInfo* info) { + if (!info) { + return ANDROID_BITMAP_FORMAT_NONE; + } + return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType)); +} + +int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) { + if (!info) { + // FIXME: Better invalid? + return -1; + } + switch (toDecoder(info)->mCodec->getInfo().alphaType()) { + case kUnknown_SkAlphaType: + LOG_ALWAYS_FATAL("Invalid alpha type"); + return -1; + case kUnpremul_SkAlphaType: + // fall through. premul is the default. + case kPremul_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; + case kOpaque_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE; + } +} + +SkAlphaType toAlphaType(int androidBitmapFlags) { + switch (androidBitmapFlags) { + case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL: + return kPremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL: + return kUnpremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE: + return kOpaque_SkAlphaType; + default: + return kUnknown_SkAlphaType; + } +} + +int AImageDecoder_setAlphaFlags(AImageDecoder* decoder, int alphaFlag) { + if (!decoder || alphaFlag < ANDROID_BITMAP_FLAGS_ALPHA_PREMUL + || alphaFlag > ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return toDecoder(decoder)->setOutAlphaType(toAlphaType(alphaFlag)) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; +} + +int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) { + if (!decoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return toDecoder(decoder)->setTargetSize(width, height) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE; +} + +int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) { + if (!decoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + SkIRect cropIRect; + cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom); + SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect; + return toDecoder(decoder)->setCropRect(cropPtr) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER; +} + + +size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) { + if (!decoder) { + return 0; + } + + SkImageInfo info = toDecoder(decoder)->getOutputInfo(); + return info.minRowBytes(); +} + +int AImageDecoder_decodeImage(AImageDecoder* decoder, + void* pixels, size_t stride, + size_t size) { + if (!decoder || !pixels || !stride) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + ImageDecoder* imageDecoder = toDecoder(decoder); + + const int height = imageDecoder->getOutputInfo().height(); + const size_t minStride = AImageDecoder_getMinimumStride(decoder); + // If this calculation were to overflow, it would have been caught in + // setTargetSize. + if (stride < minStride || size < stride * (height - 1) + minStride) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return ResultToErrorCode(imageDecoder->decode(pixels, stride)); +} + +void AImageDecoder_delete(AImageDecoder* decoder) { + delete toDecoder(decoder); +} diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt index 6adb95520d6c..bdd7f63b2d78 100644 --- a/native/graphics/jni/libjnigraphics.map.txt +++ b/native/graphics/jni/libjnigraphics.map.txt @@ -1,7 +1,23 @@ LIBJNIGRAPHICS { global: + AImageDecoder_createFromAAsset; + AImageDecoder_createFromFd; + AImageDecoder_createFromBuffer; + AImageDecoder_delete; + AImageDecoder_setAndroidBitmapFormat; + AImageDecoder_setAlphaFlags; + AImageDecoder_getHeaderInfo; + AImageDecoder_getMinimumStride; + AImageDecoder_decodeImage; + AImageDecoder_setTargetSize; + AImageDecoder_setCrop; + AImageDecoderHeaderInfo_getWidth; + AImageDecoderHeaderInfo_getHeight; + AImageDecoderHeaderInfo_getMimeType; + AImageDecoderHeaderInfo_getAlphaFlags; + AImageDecoderHeaderInfo_isAnimated; + AImageDecoderHeaderInfo_getAndroidBitmapFormat; AndroidBitmap_getInfo; - AndroidBitmap_getDataSpace; AndroidBitmap_lockPixels; AndroidBitmap_unlockPixels; local: diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml new file mode 100644 index 000000000000..cc36e87eb480 --- /dev/null +++ b/packages/CarSystemUI/res/layout/sysui_primary_window.xml @@ -0,0 +1,30 @@ +<?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. + --> + +<!-- Fullscreen views in sysui should be listed here in increasing Z order. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:background="@android:color/transparent" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ViewStub android:id="@+id/fullscreen_user_switcher_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout="@layout/car_fullscreen_user_switcher"/> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java index f7802d205a3a..cfe1c702663e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -19,14 +19,19 @@ import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.util.leak.LeakDetector; import javax.inject.Inject; import javax.inject.Singleton; +import dagger.Lazy; + /** * Car specific notification entry manager that does nothing when adding a notification. * @@ -42,8 +47,12 @@ public class CarNotificationEntryManager extends NotificationEntryManager { NotificationGroupManager groupManager, NotificationRankingManager rankingManager, KeyguardEnvironment keyguardEnvironment, - FeatureFlags featureFlags) { - super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags); + FeatureFlags featureFlags, + Lazy<NotificationRowBinder> notificationRowBinderLazy, + Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, + LeakDetector leakDetector) { + super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags, + notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector); } @Override diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java new file mode 100644 index 000000000000..c7e14d677b04 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.car; + +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.os.Binder; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.policy.ConfigurationController; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controls the expansion state of the primary window which will contain all of the fullscreen sysui + * behavior. This window still has a collapsed state in order to watch for swipe events to expand + * this window for the notification panel. + */ +@Singleton +public class SystemUIPrimaryWindowController implements + ConfigurationController.ConfigurationListener { + + private final Context mContext; + private final Resources mResources; + private final WindowManager mWindowManager; + + private final int mStatusBarHeight; + private final int mNavBarHeight; + private final int mDisplayHeight; + private ViewGroup mBaseLayout; + private WindowManager.LayoutParams mLp; + private WindowManager.LayoutParams mLpChanged; + private boolean mIsAttached = false; + + @Inject + public SystemUIPrimaryWindowController( + Context context, + @Main Resources resources, + WindowManager windowManager, + ConfigurationController configurationController + ) { + mContext = context; + mResources = resources; + mWindowManager = windowManager; + + Point display = new Point(); + mWindowManager.getDefaultDisplay().getSize(display); + mDisplayHeight = display.y; + + mStatusBarHeight = mResources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height); + + mLpChanged = new WindowManager.LayoutParams(); + mBaseLayout = (ViewGroup) LayoutInflater.from(context) + .inflate(R.layout.sysui_primary_window, /* root= */ null, false); + + configurationController.addCallback(this); + } + + /** Returns the base view of the primary window. */ + public ViewGroup getBaseLayout() { + return mBaseLayout; + } + + /** Returns {@code true} if the window is already attached. */ + public boolean isAttached() { + return mIsAttached; + } + + /** Attaches the window to the window manager. */ + public void attach() { + if (mIsAttached) { + return; + } + mIsAttached = true; + // Now that the status bar window encompasses the sliding panel and its + // translucent backdrop, the entire thing is made TRANSLUCENT and is + // hardware-accelerated. + mLp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + mStatusBarHeight, + WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + PixelFormat.TRANSLUCENT); + mLp.token = new Binder(); + mLp.gravity = Gravity.TOP; + mLp.setFitWindowInsetsTypes(/* types= */ 0); + mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + mLp.setTitle("SystemUIPrimaryWindow"); + mLp.packageName = mContext.getPackageName(); + mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + + mWindowManager.addView(mBaseLayout, mLp); + mLpChanged.copyFrom(mLp); + } + + /** Sets the window to the expanded state. */ + public void setWindowExpanded(boolean expanded) { + if (expanded) { + // TODO: Update this so that the windowing type gets the full height of the display + // when we use MATCH_PARENT. + mLpChanged.height = mDisplayHeight + mNavBarHeight; + mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + } else { + mLpChanged.height = mStatusBarHeight; + // TODO: Allow touches to go through to the status bar to handle notification panel. + mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + } + updateWindow(); + } + + /** Returns {@code true} if the window is expanded */ + public boolean isWindowExpanded() { + return mLp.height != mStatusBarHeight; + } + + private void updateWindow() { + if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { + if (isAttached()) { + mWindowManager.updateViewLayout(mBaseLayout, mLp); + } + } + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 3c3ebe2fe228..18485dc283f0 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.car; -import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import android.animation.Animator; @@ -68,6 +67,7 @@ import com.android.systemui.bubbles.BubbleController; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.CarDeviceProvisionedListener; import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.SystemUIPrimaryWindowController; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.UiBackground; @@ -107,7 +107,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -168,6 +169,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt // acceleration rate for the fling animation private static final float FLING_SPEED_UP_FACTOR = 0.6f; + private final UserSwitcherController mUserSwitcherController; private final ScrimController mScrimController; private final LockscreenLockIconController mLockscreenLockIconController; @@ -177,17 +179,16 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt private float mBackgroundAlphaDiff; private float mInitialBackgroundAlpha; - private final Lazy<FullscreenUserSwitcher> mFullscreenUserSwitcherLazy; - private FullscreenUserSwitcher mFullscreenUserSwitcher; - private CarBatteryController mCarBatteryController; private BatteryMeterView mBatteryMeterView; private Drawable mNotificationPanelBackground; private final Object mQueueLock = new Object(); + private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController; private final CarNavigationBarController mCarNavigationBarController; private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder; private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy; + private final FullscreenUserSwitcher mFullscreenUserSwitcher; private final ShadeController mShadeController; private final CarServiceProvider mCarServiceProvider; private final CarDeviceProvisionedController mCarDeviceProvisionedController; @@ -268,8 +269,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -334,11 +334,13 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, + NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry, /* Car Settings injected components. */ CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, - Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy, + FullscreenUserSwitcher fullscreenUserSwitcher, + SystemUIPrimaryWindowController systemUIPrimaryWindowController, CarNavigationBarController carNavigationBarController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { super( @@ -355,8 +357,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - allowNotificationLongPress, - newNotifPipeline, + notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, @@ -421,7 +422,9 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt keyguardDismissUtil, extensionController, userInfoControllerImpl, + notificationRowBinder, dismissCallbackRegistry); + mUserSwitcherController = userSwitcherController; mScrimController = scrimController; mLockscreenLockIconController = lockscreenLockIconController; mCarDeviceProvisionedController = @@ -429,7 +432,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt mShadeController = shadeController; mCarServiceProvider = carServiceProvider; mPowerManagerHelperLazy = powerManagerHelperLazy; - mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy; + mFullscreenUserSwitcher = fullscreenUserSwitcher; + mSystemUIPrimaryWindowController = systemUIPrimaryWindowController; mCarNavigationBarController = carNavigationBarController; mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; } @@ -444,6 +448,13 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt mScreenLifecycle = Dependency.get(ScreenLifecycle.class); mScreenLifecycle.addObserver(mScreenObserver); + // TODO: Remove the setup of user switcher from Car Status Bar. + mSystemUIPrimaryWindowController.attach(); + mFullscreenUserSwitcher.setStatusBar(this); + mFullscreenUserSwitcher.setContainer( + mSystemUIPrimaryWindowController.getBaseLayout().findViewById( + R.id.fullscreen_user_switcher_stub)); + // Notification bar related setup. mInitialBackgroundAlpha = (float) mContext.getResources().getInteger( R.integer.config_initialNotificationBackgroundAlpha) / 100; @@ -510,16 +521,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt }); } - /** - * Allows for showing or hiding just the navigation bars. This is indented to be used when - * the full screen user selector is shown. - */ - void setNavBarVisibility(@View.Visibility int visibility) { - mCarNavigationBarController.setBottomWindowVisibility(visibility); - mCarNavigationBarController.setLeftWindowVisibility(visibility); - mCarNavigationBarController.setRightWindowVisibility(visibility); - } - @Override public boolean hideKeyguard() { boolean result = super.hideKeyguard(); @@ -924,9 +925,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt + " scroll " + mStackScroller.getScrollX() + "," + mStackScroller.getScrollY()); } - - pw.print(" mFullscreenUserSwitcher="); - pw.println(mFullscreenUserSwitcher); pw.print(" mCarBatteryController="); pw.println(mCarBatteryController); pw.print(" mBatteryMeterView="); @@ -972,14 +970,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt @Override protected void createUserSwitcher() { - UserSwitcherController userSwitcherController = - Dependency.get(UserSwitcherController.class); - if (userSwitcherController.useFullscreenUserSwitcher()) { - mFullscreenUserSwitcher = mFullscreenUserSwitcherLazy.get(); - mFullscreenUserSwitcher.setStatusBar(this); - mFullscreenUserSwitcher.setContainer( - mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub)); - } else { + if (!mUserSwitcherController.useFullscreenUserSwitcher()) { super.createUserSwitcher(); } } @@ -996,25 +987,12 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt super.onStateChanged(newState); if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) { - hideUserSwitcher(); + mFullscreenUserSwitcher.hide(); } else { dismissKeyguardWhenUserSwitcherNotDisplayed(); } } - /** Makes the full screen user switcher visible, if applicable. */ - public void showUserSwitcher() { - if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) { - mFullscreenUserSwitcher.show(); // Makes the switcher visible. - } - } - - private void hideUserSwitcher() { - if (mFullscreenUserSwitcher != null) { - mFullscreenUserSwitcher.hide(); - } - } - final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurnedOn() { @@ -1024,7 +1002,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt // We automatically dismiss keyguard unless user switcher is being shown on the keyguard. private void dismissKeyguardWhenUserSwitcherNotDisplayed() { - if (mFullscreenUserSwitcher == null) { + if (!mUserSwitcherController.useFullscreenUserSwitcher()) { return; // Not using the full screen user switcher. } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java index 0ad0992b68a4..2a2eb6976653 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java @@ -24,6 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.R; import com.android.systemui.dock.DockManager; +import com.android.systemui.navigationbar.car.CarNavigationBarController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.NavigationModeController; @@ -40,6 +41,8 @@ import javax.inject.Singleton; public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager { protected boolean mShouldHideNavBar; + private final CarNavigationBarController mCarNavigationBarController; + private final FullscreenUserSwitcher mFullscreenUserSwitcher; @Inject public CarStatusBarKeyguardViewManager(Context context, @@ -52,13 +55,17 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage DockManager dockManager, StatusBarWindowController statusBarWindowController, KeyguardStateController keyguardStateController, - NotificationMediaManager notificationMediaManager) { + NotificationMediaManager notificationMediaManager, + CarNavigationBarController carNavigationBarController, + FullscreenUserSwitcher fullscreenUserSwitcher) { super(context, callback, lockPatternUtils, sysuiStatusBarStateController, configurationController, keyguardUpdateMonitor, navigationModeController, dockManager, statusBarWindowController, keyguardStateController, notificationMediaManager); mShouldHideNavBar = context.getResources() .getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown); + mCarNavigationBarController = carNavigationBarController; + mFullscreenUserSwitcher = fullscreenUserSwitcher; } @Override @@ -66,8 +73,10 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage if (!mShouldHideNavBar) { return; } - CarStatusBar statusBar = (CarStatusBar) mStatusBar; - statusBar.setNavBarVisibility(navBarVisible ? View.VISIBLE : View.GONE); + int visibility = navBarVisible ? View.VISIBLE : View.GONE; + mCarNavigationBarController.setBottomWindowVisibility(visibility); + mCarNavigationBarController.setLeftWindowVisibility(visibility); + mCarNavigationBarController.setRightWindowVisibility(visibility); } /** @@ -86,8 +95,7 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage */ @Override public void onCancelClicked() { - CarStatusBar statusBar = (CarStatusBar) mStatusBar; - statusBar.showUserSwitcher(); + mFullscreenUserSwitcher.show(); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index a1eccceea771..3abbe32df2da 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.car; -import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import android.content.Context; @@ -32,6 +31,7 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.SystemUIPrimaryWindowController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.keyguard.DismissCallbackRegistry; @@ -67,7 +67,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -138,8 +139,7 @@ public class CarStatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -204,10 +204,12 @@ public class CarStatusBarModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, + NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry, CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, - Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy, + FullscreenUserSwitcher fullscreenUserSwitcher, + SystemUIPrimaryWindowController systemUIPrimaryWindowController, CarNavigationBarController carNavigationBarController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { return new CarStatusBar( @@ -224,8 +226,7 @@ public class CarStatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - allowNotificationLongPress, - newNotifPipeline, + notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, @@ -289,10 +290,12 @@ public class CarStatusBarModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, + notificationRowBinder, dismissCallbackRegistry, carServiceProvider, powerManagerHelperLazy, - fullscreenUserSwitcherLazy, + fullscreenUserSwitcher, + systemUIPrimaryWindowController, carNavigationBarController, flingAnimationUtilsBuilder); } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java index f8fc3bbefb01..3cd66c232717 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java @@ -38,6 +38,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.SystemUIPrimaryWindowController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener; import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord; @@ -56,9 +57,10 @@ public class FullscreenUserSwitcher { private final UserManager mUserManager; private final CarServiceProvider mCarServiceProvider; private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper; + private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController; + private CarStatusBar mCarStatusBar; private final int mShortAnimDuration; - private CarStatusBar mStatusBar; private View mParent; private UserGridRecyclerView mUserGridView; private CarTrustAgentEnrollmentManager mEnrollmentManager; @@ -81,23 +83,35 @@ public class FullscreenUserSwitcher { @Main Resources resources, UserManager userManager, CarServiceProvider carServiceProvider, - CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper) { + CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper, + SystemUIPrimaryWindowController systemUIPrimaryWindowController) { mContext = context; mResources = resources; mUserManager = userManager; mCarServiceProvider = carServiceProvider; mUnlockDialogHelper = carTrustAgentUnlockDialogHelper; + mSystemUIPrimaryWindowController = systemUIPrimaryWindowController; mShortAnimDuration = mResources.getInteger(android.R.integer.config_shortAnimTime); } - /** Sets the status bar which controls the keyguard. */ + /** Sets the status bar which gives an entry point to dismiss the keyguard. */ + // TODO: Remove this in favor of a keyguard controller. public void setStatusBar(CarStatusBar statusBar) { - mStatusBar = statusBar; + mCarStatusBar = statusBar; + } + + /** Returns {@code true} if the user switcher already has a parent view. */ + public boolean isAttached() { + return mParent != null; } /** Sets the {@link ViewStub} to show the user switcher. */ public void setContainer(ViewStub containerStub) { + if (isAttached()) { + return; + } + mParent = containerStub.inflate(); View container = mParent.findViewById(R.id.container); @@ -148,20 +162,31 @@ public class FullscreenUserSwitcher { * Makes user grid visible. */ public void show() { + if (!isAttached()) { + return; + } mParent.setVisibility(View.VISIBLE); + mSystemUIPrimaryWindowController.setWindowExpanded(true); } /** * Hides the user grid. */ public void hide() { + if (!isAttached()) { + return; + } mParent.setVisibility(View.INVISIBLE); + mSystemUIPrimaryWindowController.setWindowExpanded(false); } /** * @return {@code true} if user grid is visible, {@code false} otherwise. */ public boolean isVisible() { + if (!isAttached()) { + return false; + } return mParent.getVisibility() == View.VISIBLE; } @@ -196,7 +221,7 @@ public class FullscreenUserSwitcher { } if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) { hide(); - mStatusBar.dismissKeyguard(); + mCarStatusBar.dismissKeyguard(); return; } // Switching is about to happen, since it takes time, fade out the switcher gradually. diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java index cdabeebe2819..8c756ecbaefc 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java @@ -43,6 +43,9 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; @@ -53,7 +56,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.util.UserIcons; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -337,7 +339,7 @@ public class UserGridRecyclerView extends RecyclerView { .setPositiveButton(android.R.string.ok, null) .create(); // Sets window flags for the SysUI dialog - SystemUIDialog.applyFlags(maxUsersDialog); + applyCarSysUIDialogFlags(maxUsersDialog); maxUsersDialog.show(); } @@ -356,10 +358,19 @@ public class UserGridRecyclerView extends RecyclerView { .setOnCancelListener(this) .create(); // Sets window flags for the SysUI dialog - SystemUIDialog.applyFlags(addUserDialog); + applyCarSysUIDialogFlags(addUserDialog); addUserDialog.show(); } + private void applyCarSysUIDialogFlags(AlertDialog dialog) { + final Window window = dialog.getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + window.setFitWindowInsetsTypes( + window.getFitWindowInsetsTypes() & ~WindowInsets.Type.statusBars()); + } + private void notifyUserSelected(UserRecord userRecord) { // Notify the listener which user was selected if (mUserSelectionListener != null) { diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java index 642dc82c4d28..41a9b24afa03 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -36,6 +36,7 @@ import android.net.http.SslError; import android.os.Bundle; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -49,7 +50,6 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.ArrayUtils; import com.android.internal.util.TrafficStatsConstants; @@ -203,7 +203,7 @@ public class CaptivePortalLoginActivity extends Activity { } private URL getUrlForCaptivePortal() { - String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY); + String url = getIntent().getStringExtra(TelephonyManager.EXTRA_REDIRECTION_URL); if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl(); final CarrierConfigManager configManager = getApplicationContext() .getSystemService(CarrierConfigManager.class); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java index 2697a1066ed2..cb062a63541e 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java @@ -30,8 +30,6 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import com.android.internal.telephony.PhoneConstants; - /** * This util class provides common logic for carrier actions */ @@ -103,7 +101,7 @@ public class CarrierActionUtils { } private static void onDisableAllMeteredApns(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onDisableAllMeteredApns subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -111,7 +109,7 @@ public class CarrierActionUtils { } private static void onEnableAllMeteredApns(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onEnableAllMeteredApns subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -135,7 +133,7 @@ public class CarrierActionUtils { } private static void onRegisterDefaultNetworkAvail(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onRegisterDefaultNetworkAvail subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -143,7 +141,7 @@ public class CarrierActionUtils { } private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onDeregisterDefaultNetworkAvail subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -151,7 +149,7 @@ public class CarrierActionUtils { } private static void onDisableRadio(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onDisableRadio subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -159,7 +157,7 @@ public class CarrierActionUtils { } private static void onEnableRadio(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onEnableRadio subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -202,7 +200,7 @@ public class CarrierActionUtils { } private static void onResetAllCarrierActions(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onResetAllCarrierActions subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java index 46b1d5fe27be..c7f5e9a5ceec 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java @@ -19,10 +19,10 @@ import android.content.Context; import android.content.Intent; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; @@ -50,7 +50,7 @@ public class CustomConfigLoader { * @param intent passing signal for config match * @return a list of carrier action for the given signal based on the carrier config. * - * Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED + * Example: input intent TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED * This intent allows fined-grained matching based on both intent type & extra values: * apnType and errorCode. * apnType read from passing intent is "default" and errorCode is 0x26 for example and @@ -78,25 +78,25 @@ public class CustomConfigLoader { String arg1 = null; String arg2 = null; switch (intent.getAction()) { - case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED: + case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY); break; - case TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED: + case TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY); - arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY); - arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY); + arg1 = intent.getStringExtra(TelephonyManager.EXTRA_APN_TYPE); + arg2 = intent.getStringExtra(TelephonyManager.EXTRA_ERROR_CODE); break; - case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET: + case TelephonyManager.ACTION_CARRIER_SIGNAL_RESET: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET); break; - case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE: + case TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE); - arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents - .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false)); + arg1 = String.valueOf(intent.getBooleanExtra(TelephonyManager + .EXTRA_DEFAULT_NETWORK_AVAILABLE, false)); break; default: Log.e(TAG, "load carrier config failure with un-configured key: " diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java index 3e34f0aa6124..78a02d71fc9f 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java @@ -27,10 +27,9 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.provider.Settings; +import android.telephony.TelephonyManager; import android.util.Log; -import com.android.internal.telephony.TelephonyIntents; - /** * Service to run {@link android.app.job.JobScheduler} job. * Service to monitor when there is a change to conent URI @@ -93,7 +92,7 @@ public class ProvisionObserver extends JobService { } int jobId; switch(intent.getAction()) { - case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED: + case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED: jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID; break; default: diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java index 1928ad9add3e..6229434d1d86 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java @@ -15,18 +15,22 @@ */ package com.android.carrierdefaultapp; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; -import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.TelephonyIntents; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -34,10 +38,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; public class CarrierDefaultReceiverTest extends InstrumentationTestCase { @Mock @@ -69,6 +69,7 @@ public class CarrierDefaultReceiverTest extends InstrumentationTestCase { mContext.injectSystemService(NotificationManager.class, mNotificationMgr); mContext.injectSystemService(TelephonyManager.class, mTelephonyMgr); mContext.injectSystemService(CarrierConfigManager.class, mCarrierConfigMgr); + doReturn(mTelephonyMgr).when(mTelephonyMgr).createForSubscriptionId(anyInt()); mReceiver = new CarrierDefaultBroadcastReceiver(); } @@ -87,8 +88,8 @@ public class CarrierDefaultReceiverTest extends InstrumentationTestCase { .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, new String[]{"4,1"}); doReturn(b).when(mCarrierConfigMgr).getConfig(); - Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED); - intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED); + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); mReceiver.onReceive(mContext, intent); mContext.waitForMs(100); diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java index 69bd0ed0c59c..ff00fb3282b1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java @@ -16,8 +16,6 @@ package com.android.settingslib; -import static android.content.Context.TELEPHONY_SERVICE; - import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -172,36 +170,38 @@ public class DeviceInfoUtils { } } - public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) { + /** + * Format a phone number. + * @param subscriptionInfo {@link SubscriptionInfo} subscription information. + * @return Returns formatted phone number. + */ + public static String getFormattedPhoneNumber(Context context, + SubscriptionInfo subscriptionInfo) { String formattedNumber = null; if (subscriptionInfo != null) { - final TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); - final String rawNumber = - telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId()); + final TelephonyManager telephonyManager = context.getSystemService( + TelephonyManager.class); + final String rawNumber = telephonyManager.createForSubscriptionId( + subscriptionInfo.getSubscriptionId()).getLine1Number(); if (!TextUtils.isEmpty(rawNumber)) { formattedNumber = PhoneNumberUtils.formatNumber(rawNumber); } - } return formattedNumber; } public static String getFormattedPhoneNumbers(Context context, - List<SubscriptionInfo> subscriptionInfo) { + List<SubscriptionInfo> subscriptionInfoList) { StringBuilder sb = new StringBuilder(); - if (subscriptionInfo != null) { - final TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); - final int count = subscriptionInfo.size(); - for (int i = 0; i < count; i++) { - final String rawNumber = telephonyManager.getLine1Number( - subscriptionInfo.get(i).getSubscriptionId()); + if (subscriptionInfoList != null) { + final TelephonyManager telephonyManager = context.getSystemService( + TelephonyManager.class); + final int count = subscriptionInfoList.size(); + for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) { + final String rawNumber = telephonyManager.createForSubscriptionId( + subscriptionInfo.getSubscriptionId()).getLine1Number(); if (!TextUtils.isEmpty(rawNumber)) { - sb.append(PhoneNumberUtils.formatNumber(rawNumber)); - if (i < count - 1) { - sb.append("\n"); - } + sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n"); } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java index ebca962a918e..853c77efd4e9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java @@ -38,7 +38,7 @@ public class DataUsageUtils { final SubscriptionManager subscriptionManager = context.getSystemService( SubscriptionManager.class); final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( - telephonyManager.getSubscriberId(subId)); + telephonyManager.createForSubscriptionId(subId).getSubscriberId()); if (!subscriptionManager.isActiveSubId(subId)) { Log.i(TAG, "Subscription is not active: " + subId); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 81739e069e28..9d4c24e8faa4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -211,13 +211,6 @@ public class AccessPoint implements Comparable<AccessPoint> { private static final int EAP_WPA = 1; // WPA-EAP private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP - /** - * The number of distinct wifi levels. - * - * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}. - */ - public static final int SIGNAL_LEVELS = 5; - public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; public static final String KEY_PREFIX_AP = "AP:"; @@ -453,9 +446,10 @@ public class AccessPoint implements Comparable<AccessPoint> { return other.getSpeed() - getSpeed(); } + WifiManager wifiManager = getWifiManager(); // Sort by signal strength, bucketed by level - int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS) - - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); + int difference = wifiManager.calculateSignalLevel(other.mRssi) + - wifiManager.calculateSignalLevel(mRssi); if (difference != 0) { return difference; } @@ -869,13 +863,14 @@ public class AccessPoint implements Comparable<AccessPoint> { } /** - * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1. + * Returns the number of levels to show for a Wifi icon, from 0 to + * {@link WifiManager#getMaxSignalLevel()}. * - * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will + * <p>Use {@link #isReachable()} to determine if an AccessPoint is in range, as this method will * always return at least 0. */ public int getLevel() { - return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); + return getWifiManager().calculateSignalLevel(mRssi); } public int getRssi() { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java index 4a4f0e66cfe8..f21e466dd8ab 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java @@ -22,6 +22,7 @@ import android.net.NetworkInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Parcelable; @@ -126,13 +127,15 @@ public class TestAccessPointBuilder { @Keep public TestAccessPointBuilder setLevel(int level) { // Reversal of WifiManager.calculateSignalLevels + WifiManager wifiManager = mContext.getSystemService(WifiManager.class); + int maxSignalLevel = wifiManager.getMaxSignalLevel(); if (level == 0) { mRssi = MIN_RSSI; - } else if (level >= AccessPoint.SIGNAL_LEVELS) { + } else if (level > maxSignalLevel) { mRssi = MAX_RSSI; } else { float inputRange = MAX_RSSI - MIN_RSSI; - float outputRange = AccessPoint.SIGNAL_LEVELS - 1; + float outputRange = maxSignalLevel; mRssi = (int) (level * inputRange / outputRange + MIN_RSSI); } return this; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index 8591116fce0f..3f55ceaad29a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -89,7 +89,7 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback { public void setListening(boolean listening) { if (listening) { mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, - mWifiNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); + mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); mWifiNetworkScoreCache.registerListener(mCacheListener); mConnectivityManager.registerNetworkCallback( mNetworkRequest, mNetworkCallback, mHandler); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index ba6a8ea31987..ed4ff085aeac 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -361,7 +361,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mNetworkScoreManager.registerNetworkScoreCache( NetworkKey.TYPE_WIFI, mScoreCache, - NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); + NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); } private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) { diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp index 4600793729a2..2ccff1ecaf6c 100644 --- a/packages/SettingsLib/tests/integ/Android.bp +++ b/packages/SettingsLib/tests/integ/Android.bp @@ -14,7 +14,10 @@ android_test { name: "SettingsLibTests", - defaults: ["SettingsLibDefaults"], + defaults: [ + "SettingsLibDefaults", + "framework-wifi-test-defaults" + ], certificate: "platform", diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 61cbbd3eb0a4..03201ae6d5ba 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -83,7 +83,6 @@ import java.util.concurrent.CountDownLatch; @SmallTest @RunWith(AndroidJUnit4.class) public class AccessPointTest { - private static final String TEST_SSID = "\"test_ssid\""; private static final String ROAMING_SSID = "\"roaming_ssid\""; private static final String OSU_FRIENDLY_NAME = "osu_friendly_name"; @@ -98,6 +97,7 @@ public class AccessPointTest { 20 * DateUtils.MINUTE_IN_MILLIS; private Context mContext; + private int mMaxSignalLevel; private WifiInfo mWifiInfo; @Mock private Context mMockContext; @Mock private WifiManager mMockWifiManager; @@ -128,6 +128,7 @@ public class AccessPointTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getTargetContext(); + mMaxSignalLevel = mContext.getSystemService(WifiManager.class).getMaxSignalLevel(); mWifiInfo = new WifiInfo(); mWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID)); mWifiInfo.setBSSID(TEST_BSSID); @@ -180,7 +181,7 @@ public class AccessPointTest { @Test public void testCompareTo_GivesHighLevelBeforeLowLevel() { - final int highLevel = AccessPoint.SIGNAL_LEVELS - 1; + final int highLevel = mMaxSignalLevel; final int lowLevel = 1; assertThat(highLevel).isGreaterThan(lowLevel); @@ -226,7 +227,7 @@ public class AccessPointTest { .setReachable(true).build(); AccessPoint saved = new TestAccessPointBuilder(mContext).setSaved(true).build(); AccessPoint highLevelAndReachable = new TestAccessPointBuilder(mContext) - .setLevel(AccessPoint.SIGNAL_LEVELS - 1).build(); + .setLevel(mMaxSignalLevel).build(); AccessPoint firstName = new TestAccessPointBuilder(mContext).setSsid("a").build(); AccessPoint lastname = new TestAccessPointBuilder(mContext).setSsid("z").build(); @@ -520,6 +521,8 @@ public class AccessPointTest { when(packageManager.getApplicationInfoAsUser(eq(appPackageName), anyInt(), anyInt())) .thenReturn(applicationInfo); when(applicationInfo.loadLabel(packageManager)).thenReturn(appLabel); + when(context.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); + when(mMockWifiManager.calculateSignalLevel(rssi)).thenReturn(4); NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", ""); @@ -636,14 +639,14 @@ public class AccessPointTest { public void testBuilder_setLevel() { AccessPoint testAp; - for (int i = 0; i < AccessPoint.SIGNAL_LEVELS; i++) { + for (int i = 0; i <= mMaxSignalLevel; i++) { testAp = new TestAccessPointBuilder(mContext).setLevel(i).build(); assertThat(testAp.getLevel()).isEqualTo(i); } // numbers larger than the max level should be set to max - testAp = new TestAccessPointBuilder(mContext).setLevel(AccessPoint.SIGNAL_LEVELS).build(); - assertThat(testAp.getLevel()).isEqualTo(AccessPoint.SIGNAL_LEVELS - 1); + testAp = new TestAccessPointBuilder(mContext).setLevel(mMaxSignalLevel + 1).build(); + assertThat(testAp.getLevel()).isEqualTo(mMaxSignalLevel); // numbers less than 0 should give level 0 testAp = new TestAccessPointBuilder(mContext).setLevel(-100).build(); @@ -653,7 +656,7 @@ public class AccessPointTest { @Test public void testBuilder_settingReachableAfterLevelDoesNotAffectLevel() { int level = 1; - assertThat(level).isLessThan(AccessPoint.SIGNAL_LEVELS - 1); + assertThat(level).isLessThan(mMaxSignalLevel); AccessPoint testAp = new TestAccessPointBuilder(mContext).setLevel(level).setReachable(true).build(); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 347d6c2264eb..1c63efc3b5bc 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -194,6 +194,9 @@ <uses-permission android:name="android.permission.MANAGE_APPOPS" /> + <!-- Permission required for storage tests - FuseDaemonHostTest --> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> + <!-- Permission needed to run network tests in CTS --> <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" /> <!-- Permission needed to test tcp keepalive offload. --> diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml index bc1233828b4d..0f9deaa3c569 100644 --- a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml +++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml @@ -57,15 +57,5 @@ android:textColor="@color/global_actions_text" android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView - android:visibility="gone" - android:id="@*android:id/status" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:textColor="@color/global_actions_text" - android:textAppearance="?android:attr/textAppearanceSmall" - /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml index 4404c87432fe..31c7cbf6ff1b 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_item.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml @@ -56,15 +56,5 @@ android:textColor="@color/global_actions_text" android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView - android:visibility="gone" - android:id="@*android:id/status" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:textColor="@color/global_actions_text" - android:textAppearance="?android:attr/textAppearanceSmall" - /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml new file mode 100644 index 000000000000..50aa212c94a6 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingTop="@dimen/global_actions_grid_item_vertical_margin" + android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin" + android:paddingLeft="@dimen/global_actions_grid_item_side_margin" + android:paddingRight="@dimen/global_actions_grid_item_side_margin" + android:layout_marginRight="3dp" + android:layout_marginLeft="3dp" + android:background="@drawable/rounded_bg_full"> + <LinearLayout + android:layout_width="@dimen/global_actions_grid_item_width" + android:layout_height="@dimen/global_actions_grid_item_height" + android:gravity="top|center_horizontal" + android:orientation="vertical"> + <ImageView + android:id="@*android:id/icon" + android:layout_width="@dimen/global_actions_grid_item_icon_width" + android:layout_height="@dimen/global_actions_grid_item_icon_height" + android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin" + android:scaleType="centerInside" + android:tint="@color/global_actions_text" /> + + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:singleLine="true" + android:gravity="center" + android:textSize="12dp" + android:textColor="@color/global_actions_text" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + <TextView + android:visibility="gone" + android:id="@*android:id/status" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/global_actions_text" + android:textAppearance="?android:attr/textAppearanceSmall" /> + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml new file mode 100644 index 000000000000..4cfb47e3c642 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/global_actions_grid_root" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"> + + <com.android.systemui.globalactions.GlobalActionsFlatLayout + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:theme="@style/qs_theme" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:gravity="top | center_horizontal" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" + android:layout_marginTop="@dimen/global_actions_top_margin" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layoutDirection="ltr" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin"> + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:orientation="vertical" + android:gravity="center" + android:translationZ="@dimen/global_actions_translate" + /> + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="right" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> + </com.android.systemui.globalactions.GlobalActionsFlatLayout> + + <LinearLayout + android:id="@+id/global_actions_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@id/global_actions_view"> + + <FrameLayout + android:translationY="@dimen/global_actions_plugin_offset" + android:id="@+id/global_actions_panel_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout> + + <LinearLayout + android:translationY="@dimen/global_actions_plugin_offset" + android:id="@+id/global_actions_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@id/global_actions_panel"> + <TextView + android:text="Home" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:gravity="center" + android:textSize="25dp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="@*android:string/config_headlineFontFamily" /> + <LinearLayout + android:id="@+id/global_actions_controls_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> + </LinearLayout> + </androidx.constraintlayout.widget.ConstraintLayout> +</ScrollView> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 6ac9da4291c6..995cb7d48e60 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2011 The Android Open Source Project +<!-- Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml new file mode 100644 index 000000000000..791c7ea875fe --- /dev/null +++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView android:id="@+id/global_screenshot_legacy_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@android:color/black" + android:visibility="gone" /> + <ImageView android:id="@+id/global_screenshot_legacy" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:background="@drawable/screenshot_panel" + android:visibility="gone" + android:adjustViewBounds="true" /> + <ImageView android:id="@+id/global_screenshot_legacy_flash" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@android:color/white" + android:visibility="gone" /> + <com.android.systemui.screenshot.ScreenshotSelectorView + android:id="@+id/global_screenshot_legacy_selector" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:pointerIcon="crosshair"/> +</FrameLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index da0323ac7357..ea5a5ebe5f7f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -288,6 +288,7 @@ <!-- Dimensions related to screenshots --> <!-- The padding on the global screenshot background image --> + <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> <dimen name="screenshot_action_container_padding">20dp</dimen> @@ -950,6 +951,9 @@ <dimen name="cell_overlay_padding">18dp</dimen> <!-- Global actions power menu --> + <dimen name="global_actions_top_margin">12dp</dimen> + <dimen name="global_actions_plugin_offset">-145dp</dimen> + <dimen name="global_actions_panel_width">120dp</dimen> <dimen name="global_actions_padding">12dp</dimen> <dimen name="global_actions_translate">9dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index 8105faa23e89..eab970626bf1 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -27,9 +27,9 @@ import android.util.Log; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import javax.inject.Inject; import javax.inject.Singleton; @@ -49,7 +49,7 @@ public class ForegroundServiceNotificationListener { public ForegroundServiceNotificationListener(Context context, ForegroundServiceController foregroundServiceController, NotificationEntryManager notificationEntryManager, - NotifCollection notifCollection) { + NotifPipeline notifPipeline) { mContext = context; mForegroundServiceController = foregroundServiceController; @@ -77,7 +77,7 @@ public class ForegroundServiceNotificationListener { }); mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender()); - notifCollection.addCollectionListener(new NotifCollectionListener() { + notifPipeline.addCollectionListener(new NotifCollectionListener() { @Override public void onEntryAdded(NotificationEntry entry) { addNotification(entry, entry.getImportance()); diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index b0831232bcd4..23d6458a30a5 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -18,7 +18,6 @@ package com.android.systemui.appops; import android.app.AppOpsManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; @@ -64,7 +63,6 @@ public class AppOpsControllerImpl implements AppOpsController, private H mBGHandler; private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>(); - private final PermissionFlagsCache mFlagsCache; private boolean mListening; @GuardedBy("mActiveItems") @@ -81,17 +79,10 @@ public class AppOpsControllerImpl implements AppOpsController, }; @Inject - public AppOpsControllerImpl(Context context, @Background Looper bgLooper, - DumpController dumpController) { - this(context, bgLooper, new PermissionFlagsCache(context), dumpController); - } - - @VisibleForTesting - protected AppOpsControllerImpl(Context context, Looper bgLooper, PermissionFlagsCache cache, - DumpController dumpController) { + public AppOpsControllerImpl(Context context, + @Background Looper bgLooper, DumpController dumpController) { mContext = context; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mFlagsCache = cache; mBGHandler = new H(bgLooper); final int numOps = OPS.length; for (int i = 0; i < numOps; i++) { @@ -209,7 +200,14 @@ public class AppOpsControllerImpl implements AppOpsController, mNotedItems.remove(item); if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); } - notifySuscribers(code, uid, packageName, false); + boolean active; + // Check if the item is also active + synchronized (mActiveItems) { + active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null; + } + if (!active) { + notifySuscribers(code, uid, packageName, false); + } } private boolean addNoted(int code, int uid, String packageName) { @@ -224,64 +222,13 @@ public class AppOpsControllerImpl implements AppOpsController, createdNew = true; } } + // We should keep this so we make sure it cannot time out. + mBGHandler.removeCallbacksAndMessages(item); mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS); return createdNew; } /** - * Does the app-op code refer to a user sensitive permission for the specified user id - * and package. Only user sensitive permission should be shown to the user by default. - * - * @param appOpCode The code of the app-op. - * @param uid The uid of the user. - * @param packageName The name of the package. - * - * @return {@code true} iff the app-op item is user sensitive - */ - private boolean isUserSensitive(int appOpCode, int uid, String packageName) { - String permission = AppOpsManager.opToPermission(appOpCode); - if (permission == null) { - return false; - } - int permFlags = mFlagsCache.getPermissionFlags(permission, - packageName, UserHandle.getUserHandleForUid(uid)); - return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0; - } - - /** - * Does the app-op item refer to an operation that should be shown to the user. - * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive - * permission should be shown to the user by default. - * - * @param item The item - * - * @return {@code true} iff the app-op item should be shown to the user - */ - private boolean isUserVisible(AppOpItem item) { - return isUserVisible(item.getCode(), item.getUid(), item.getPackageName()); - } - - - /** - * Does the app-op, uid and package name, refer to an operation that should be shown to the - * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or - * ops that refer to user sensitive permission should be shown to the user by default. - * - * @param item The item - * - * @return {@code true} iff the app-op for should be shown to the user - */ - private boolean isUserVisible(int appOpCode, int uid, String packageName) { - // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission - // which may be user senstive, so for now always show it to the user. - if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) { - return true; - } - - return isUserSensitive(appOpCode, uid, packageName); - } - - /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * * @return List of active AppOps information @@ -304,8 +251,8 @@ public class AppOpsControllerImpl implements AppOpsController, final int numActiveItems = mActiveItems.size(); for (int i = 0; i < numActiveItems; i++) { AppOpItem item = mActiveItems.get(i); - if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item)) { + if ((userId == UserHandle.USER_ALL + || UserHandle.getUserId(item.getUid()) == userId)) { list.add(item); } } @@ -314,8 +261,8 @@ public class AppOpsControllerImpl implements AppOpsController, final int numNotedItems = mNotedItems.size(); for (int i = 0; i < numNotedItems; i++) { AppOpItem item = mNotedItems.get(i); - if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item)) { + if ((userId == UserHandle.USER_ALL + || UserHandle.getUserId(item.getUid()) == userId)) { list.add(item); } } @@ -325,7 +272,21 @@ public class AppOpsControllerImpl implements AppOpsController, @Override public void onOpActiveChanged(int code, int uid, String packageName, boolean active) { - if (updateActives(code, uid, packageName, active)) { + if (DEBUG) { + Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName, + Boolean.toString(active))); + } + boolean activeChanged = updateActives(code, uid, packageName, active); + if (!activeChanged) return; // early return + // Check if the item is also noted, in that case, there's no update. + boolean alsoNoted; + synchronized (mNotedItems) { + alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null; + } + // If active is true, we only send the update if the op is not actively noted (already true) + // If active is false, we only send the update if the op is not actively noted (prevent + // early removal) + if (!alsoNoted) { mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active)); } } @@ -333,17 +294,23 @@ public class AppOpsControllerImpl implements AppOpsController, @Override public void onOpNoted(int code, int uid, String packageName, int result) { if (DEBUG) { - Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]); + Log.w(TAG, "Noted op: " + code + " with result " + + AppOpsManager.MODE_NAMES[result] + " for package " + packageName); } if (result != AppOpsManager.MODE_ALLOWED) return; - if (addNoted(code, uid, packageName)) { + boolean notedAdded = addNoted(code, uid, packageName); + if (!notedAdded) return; // early return + boolean alsoActive; + synchronized (mActiveItems) { + alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null; + } + if (!alsoActive) { mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true)); } } private void notifySuscribers(int code, int uid, String packageName, boolean active) { - if (mCallbacksByCode.containsKey(code) - && isUserVisible(code, uid, packageName)) { + if (mCallbacksByCode.containsKey(code)) { if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName); for (Callback cb: mCallbacksByCode.get(code)) { cb.onActiveStateChanged(code, uid, packageName, active); @@ -368,7 +335,7 @@ public class AppOpsControllerImpl implements AppOpsController, } - protected final class H extends Handler { + protected class H extends Handler { H(Looper looper) { super(looper); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt deleted file mode 100644 index f02c7afdb9ca..000000000000 --- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.appops - -import android.content.Context -import android.content.pm.PackageManager -import android.os.UserHandle -import android.util.ArrayMap -import com.android.internal.annotations.VisibleForTesting - -private data class PermissionFlag(val flag: Int, val timestamp: Long) - -private data class PermissionFlagKey( - val permission: String, - val packageName: String, - val user: UserHandle -) - -internal const val CACHE_EXPIRATION = 10000L - -/** - * Cache for PackageManager's PermissionFlags. - * - * Flags older than [CACHE_EXPIRATION] will be retrieved again. - */ -internal open class PermissionFlagsCache(context: Context) { - private val packageManager = context.packageManager - private val permissionFlagsCache = ArrayMap<PermissionFlagKey, PermissionFlag>() - - /** - * Retrieve permission flags from cache or PackageManager. There parameters will be passed - * directly to [PackageManager]. - * - * Calls to this method should be done from a background thread. - */ - fun getPermissionFlags(permission: String, packageName: String, user: UserHandle): Int { - val key = PermissionFlagKey(permission, packageName, user) - val now = getCurrentTime() - val value = permissionFlagsCache.getOrPut(key) { - PermissionFlag(getFlags(key), now) - } - if (now - value.timestamp > CACHE_EXPIRATION) { - val newValue = PermissionFlag(getFlags(key), now) - permissionFlagsCache.put(key, newValue) - return newValue.flag - } else { - return value.flag - } - } - - private fun getFlags(key: PermissionFlagKey) = - packageManager.getPermissionFlags(key.permission, key.packageName, key.user) - - @VisibleForTesting - protected open fun getCurrentTime() = System.currentTimeMillis() -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 97224f1234dd..ccbbb2465742 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -111,7 +111,10 @@ public class BubbleData { } private final Context mContext; + /** Bubbles that are actively in the stack. */ private final List<Bubble> mBubbles; + /** Bubbles that are being loaded but haven't been added to the stack just yet. */ + private final List<Bubble> mPendingBubbles; private Bubble mSelectedBubble; private boolean mExpanded; private final int mMaxBubbles; @@ -143,6 +146,7 @@ public class BubbleData { public BubbleData(Context context) { mContext = context; mBubbles = new ArrayList<>(); + mPendingBubbles = new ArrayList<>(); mStateChange = new Update(mBubbles); mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); } @@ -188,7 +192,15 @@ public class BubbleData { Bubble getOrCreateBubble(NotificationEntry entry) { Bubble bubble = getBubbleWithKey(entry.getKey()); if (bubble == null) { + // Check for it in pending + for (int i = 0; i < mPendingBubbles.size(); i++) { + Bubble b = mPendingBubbles.get(i); + if (b.getKey().equals(entry.getKey())) { + return b; + } + } bubble = new Bubble(entry); + mPendingBubbles.add(bubble); } else { bubble.setEntry(entry); } @@ -204,7 +216,7 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + bubble); } - + mPendingBubbles.remove(bubble); // No longer pending once we're here Bubble prevBubble = getBubbleWithKey(bubble.getKey()); suppressFlyout |= !shouldShowFlyout(bubble.getEntry()); @@ -377,6 +389,12 @@ public class BubbleData { } private void doRemove(String key, @DismissReason int reason) { + // If it was pending remove it + for (int i = 0; i < mPendingBubbles.size(); i++) { + if (mPendingBubbles.get(i).getKey().equals(key)) { + mPendingBubbles.remove(mPendingBubbles.get(i)); + } + } int indexToRemove = indexForKey(key); if (indexToRemove == -1) { return; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java index 563a0a7e43e1..31656a00c3ed 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -961,6 +961,13 @@ public class PhysicsAnimationLayout extends FrameLayout { if (view != null) { final SpringAnimation animation = (SpringAnimation) view.getTag(getTagIdForProperty(property)); + + // If the animation is null, the view was probably removed from the layout before + // the animation started. + if (animation == null) { + return; + } + if (afterCallbacks != null) { animation.addEndListener(new OneTimeEndListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java index 6744d74004f0..20917bd9dcb0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java @@ -21,9 +21,12 @@ import com.android.systemui.appops.AppOpsController; import com.android.systemui.appops.AppOpsControllerImpl; import com.android.systemui.classifier.FalsingManagerProxy; import com.android.systemui.doze.DozeHost; +import com.android.systemui.globalactions.GlobalActionsComponent; +import com.android.systemui.globalactions.GlobalActionsImpl; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.PowerNotificationWarnings; @@ -99,6 +102,17 @@ public abstract class DependencyBinder { /** */ @Binds + public abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl); + + /** + */ + @Binds + public abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager( + GlobalActionsComponent controllerImpl); + + /** + */ + @Binds public abstract LocationController provideLocationController( LocationControllerImpl controllerImpl); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java index 26337b1f24b1..3aa14a31a5d9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java @@ -26,17 +26,24 @@ import android.app.KeyguardManager; import android.app.NotificationManager; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; +import android.app.trust.TrustManager; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.IPackageManager; import android.content.res.Resources; import android.hardware.SensorPrivacyManager; +import android.media.AudioManager; +import android.net.ConnectivityManager; import android.os.Handler; import android.os.PowerManager; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; +import android.os.Vibrator; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; +import android.telecom.TelecomManager; +import android.telephony.TelephonyManager; import android.view.IWindowManager; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -66,6 +73,12 @@ public class SystemServicesModule { return context.getSystemService(AccessibilityManager.class); } + @Provides + @Singleton + static ActivityManager provideActivityManager(Context context) { + return context.getSystemService(ActivityManager.class); + } + @Singleton @Provides static AlarmManager provideAlarmManager(Context context) { @@ -74,10 +87,21 @@ public class SystemServicesModule { @Provides @Singleton - static ActivityManager provideActivityManager(Context context) { - return context.getSystemService(ActivityManager.class); + static AudioManager provideAudioManager(Context context) { + return context.getSystemService(AudioManager.class); } + @Provides + @Singleton + static ConnectivityManager provideConnectivityManagager(Context context) { + return context.getSystemService(ConnectivityManager.class); + } + + @Provides + @Singleton + static ContentResolver provideContentResolver(Context context) { + return context.getContentResolver(); + } @Provides @DisplayId @@ -185,6 +209,31 @@ public class SystemServicesModule { @Provides @Singleton + static TelecomManager provideTelecomManager(Context context) { + return context.getSystemService(TelecomManager.class); + } + + @Provides + @Singleton + static TelephonyManager provideTelephonyManager(Context context) { + return context.getSystemService(TelephonyManager.class); + } + + @Provides + @Singleton + static TrustManager provideTrustManager(Context context) { + return context.getSystemService(TrustManager.class); + } + + @Provides + @Singleton + @Nullable + static Vibrator provideVibrator(Context context) { + return context.getSystemService(Vibrator.class); + } + + @Provides + @Singleton static UserManager provideUserManager(Context context) { return context.getSystemService(UserManager.class); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 58ddda9ce720..a6fa4146300d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -30,8 +30,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.people.PeopleHubModule; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; @@ -82,6 +82,11 @@ public abstract class SystemUIModule { keyguardUpdateMonitor, dumpController); } + /** */ + @Binds + public abstract NotificationRowBinder bindNotificationRowBinder( + NotificationRowBinderImpl notificationRowBinder); + @Singleton @Provides static SysUiState provideSysUiState() { @@ -106,9 +111,4 @@ public abstract class SystemUIModule { @Singleton @Binds abstract SystemClock bindSystemClock(SystemClockImpl systemClock); - - @Singleton - @Binds - abstract NotifListBuilder bindNotifListBuilder(NotifListBuilderImpl impl); - } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java index 19b6f8232a9a..e949007a158b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java @@ -19,7 +19,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.Dependency; import com.android.systemui.SystemUI; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; @@ -29,6 +28,7 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.ExtensionController.Extension; import javax.inject.Inject; +import javax.inject.Provider; import javax.inject.Singleton; /** @@ -38,23 +38,29 @@ import javax.inject.Singleton; public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager { private final CommandQueue mCommandQueue; + private final ExtensionController mExtensionController; + private final Provider<GlobalActions> mGlobalActionsProvider; private GlobalActions mPlugin; private Extension<GlobalActions> mExtension; private IStatusBarService mBarService; @Inject - public GlobalActionsComponent(Context context, CommandQueue commandQueue) { + public GlobalActionsComponent(Context context, CommandQueue commandQueue, + ExtensionController extensionController, + Provider<GlobalActions> globalActionsProvider) { super(context); mCommandQueue = commandQueue; + mExtensionController = extensionController; + mGlobalActionsProvider = globalActionsProvider; } @Override public void start() { mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class) + mExtension = mExtensionController.newExtension(GlobalActions.class) .withPlugin(GlobalActions.class) - .withDefault(() -> new GlobalActionsImpl(mContext, mCommandQueue)) + .withDefault(mGlobalActionsProvider::get) .withCallback(this::onExtensionCallback) .build(); mPlugin = mExtension.get(); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 3ccad64d76dc..c138462d06c4 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -21,8 +21,10 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Dialog; +import android.app.IActivityManager; import android.app.KeyguardManager; import android.app.PendingIntent; import android.app.StatusBarManager; @@ -30,11 +32,13 @@ import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.media.AudioManager; @@ -44,20 +48,17 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; -import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.sysprop.TelephonyProperties; import android.telecom.TelecomManager; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.TelephonyManager; -import android.text.TextUtils; import android.util.ArraySet; import android.util.FeatureFlagUtils; import android.util.Log; @@ -92,6 +93,7 @@ import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; @@ -106,6 +108,8 @@ import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolat import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + /** * Helper to show the global actions dialog. Each item is an {@link Action} that * may show depending on whether the keyguard is showing, and whether the device @@ -146,6 +150,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final LockPatternUtils mLockPatternUtils; private final KeyguardManager mKeyguardManager; private final BroadcastDispatcher mBroadcastDispatcher; + private final ContentResolver mContentResolver; + private final Resources mResources; + private final UserManager mUserManager; + private final TrustManager mTrustManager; + private final IActivityManager mIActivityManager; + private final TelecomManager mTelecomManager; + private final MetricsLogger mMetricsLogger; private ArrayList<Action> mItems; private ActionsDialog mDialog; @@ -161,8 +172,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean mIsWaitingForEcmExit = false; private boolean mHasTelephony; private boolean mHasVibrator; - private boolean mHasLogoutButton; - private boolean mHasLockdownButton; private final boolean mShowSilentToggle; private final EmergencyAffordanceManager mEmergencyAffordanceManager; private final ScreenshotHelper mScreenshotHelper; @@ -173,17 +182,32 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, /** * @param context everything needs a context :( */ - public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) { + @Inject + public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs, + AudioManager audioManager, IDreamManager iDreamManager, + DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, + KeyguardManager keyguardManager, BroadcastDispatcher broadcastDispatcher, + ConnectivityManager connectivityManager, TelephonyManager telephonyManager, + ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources, + ConfigurationController configurationController, ActivityStarter activityStarter, + KeyguardStateController keyguardStateController, UserManager userManager, + TrustManager trustManager, IActivityManager iActivityManager, + TelecomManager telecomManager, MetricsLogger metricsLogger) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - mDreamManager = IDreamManager.Stub.asInterface( - ServiceManager.getService(DreamService.DREAM_SERVICE)); - mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( - Context.DEVICE_POLICY_SERVICE); - mLockPatternUtils = new LockPatternUtils(mContext); - mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class); + mAudioManager = audioManager; + mDreamManager = iDreamManager; + mDevicePolicyManager = devicePolicyManager; + mLockPatternUtils = lockPatternUtils; + mKeyguardManager = keyguardManager; + mBroadcastDispatcher = broadcastDispatcher; + mContentResolver = contentResolver; + mResources = resources; + mUserManager = userManager; + mTrustManager = trustManager; + mIActivityManager = iActivityManager; + mTelecomManager = telecomManager; + mMetricsLogger = metricsLogger; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -192,32 +216,25 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); - ConnectivityManager cm = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); // get notified of phone state changes - TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); - mContext.getContentResolver().registerContentObserver( + contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, mAirplaneModeObserver); - Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = vibrator != null && vibrator.hasVibrator(); - mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( + mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean( R.bool.config_useFixedVolume); mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); mScreenshotHelper = new ScreenshotHelper(context); mScreenRecordHelper = new ScreenRecordHelper(context); - Dependency.get(ConfigurationController.class).addCallback(this); + configurationController.addCallback(this); - mActivityStarter = Dependency.get(ActivityStarter.class); - KeyguardStateController keyguardStateController = - Dependency.get(KeyguardStateController.class); + mActivityStarter = activityStarter; keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onUnlockedChanged() { @@ -343,12 +360,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, onAirplaneModeChanged(); mItems = new ArrayList<Action>(); - String[] defaultActions = mContext.getResources().getStringArray( - R.array.config_globalActionsList); + String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList); ArraySet<String> addedKeys = new ArraySet<String>(); - mHasLogoutButton = false; - mHasLockdownButton = false; for (int i = 0; i < defaultActions.length; i++) { String actionKey = defaultActions[i]; if (addedKeys.contains(actionKey)) { @@ -360,7 +374,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { mItems.add(mAirplaneModeOn); } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { - if (Settings.Global.getInt(mContext.getContentResolver(), + if (Settings.Global.getInt(mContentResolver, Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { mItems.add(new BugReportAction()); } @@ -375,11 +389,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { mItems.add(getSettingsAction()); } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { - if (Settings.Secure.getIntForUser(mContext.getContentResolver(), + if (Settings.Secure.getIntForUser(mContentResolver, Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0 && shouldDisplayLockdown()) { mItems.add(getLockdownAction()); - mHasLockdownButton = true; } } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { mItems.add(getVoiceAssistAction()); @@ -393,7 +406,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (mDevicePolicyManager.isLogoutEnabled() && getCurrentUser().id != UserHandle.USER_SYSTEM) { mItems.add(new LogoutAction()); - mHasLogoutButton = true; } } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) { @@ -431,7 +443,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mKeyguardManager.isDeviceLocked()) : null; - ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController); + ActionsDialog dialog = new ActionsDialog( + mContext, mAdapter, panelViewController, isControlsEnabled(mContext)); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setKeyguardShowing(mKeyguardShowing); @@ -474,8 +487,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean onLongPress() { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.reboot(true); return true; } @@ -506,7 +518,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean shouldBeSeparated() { - return shouldUseSeparatedView(); + return !isControlsEnabled(mContext); } @Override @@ -560,9 +572,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public void onPress() { - MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU); - Intent intent = mContext.getSystemService(TelecomManager.class) - .createLaunchEmergencyDialerIntent(null /* number */); + mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU); + Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(null /* number */); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -579,8 +590,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean onLongPress() { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.reboot(true); return true; } @@ -618,8 +628,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public void run() { mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null); - MetricsLogger.action(mContext, - MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); + mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); } }, 500); } @@ -666,11 +675,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, public void run() { try { // Take an "interactive" bugreport. - MetricsLogger.action(mContext, + mMetricsLogger.action( MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); - if (!ActivityManager.getService().launchBugReportHandlerApp()) { + if (!mIActivityManager.launchBugReportHandlerApp()) { Log.w(TAG, "Bugreport handler could not be launched"); - ActivityManager.getService().requestInteractiveBugReport(); + mIActivityManager.requestInteractiveBugReport(); } } catch (RemoteException e) { } @@ -687,8 +696,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } try { // Take a "full" bugreport. - MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); - ActivityManager.getService().requestFullBugReport(); + mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); + mIActivityManager.requestFullBugReport(); } catch (RemoteException e) { } return false; @@ -726,8 +735,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mHandler.postDelayed(() -> { try { int currentUserId = getCurrentUser().id; - ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM); - ActivityManager.getService().stopUser(currentUserId, true /*force*/, null); + mIActivityManager.switchUser(UserHandle.USER_SYSTEM); + mIActivityManager.stopUser(currentUserId, true /*force*/, null); } catch (RemoteException re) { Log.e(TAG, "Couldn't logout user " + re); } @@ -834,20 +843,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private void lockProfiles() { - final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - final TrustManager tm = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); final int currentUserId = getCurrentUser().id; - final int[] profileIds = um.getEnabledProfileIds(currentUserId); + final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId); for (final int id : profileIds) { if (id != currentUserId) { - tm.setDeviceLockedForUser(id, true); + mTrustManager.setDeviceLockedForUser(id, true); } } } private UserInfo getCurrentUser() { try { - return ActivityManager.getService().getCurrentUser(); + return mIActivityManager.getCurrentUser(); } catch (RemoteException re) { return null; } @@ -859,9 +866,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private void addUsersToMenu(ArrayList<Action> items) { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - if (um.isUserSwitcherEnabled()) { - List<UserInfo> users = um.getUsers(); + if (mUserManager.isUserSwitcherEnabled()) { + List<UserInfo> users = mUserManager.getUsers(); UserInfo currentUser = getCurrentUser(); for (final UserInfo user : users) { if (user.supportsSwitchToByUser()) { @@ -875,7 +881,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, + (isCurrentUser ? " \u2714" : "")) { public void onPress() { try { - ActivityManager.getService().switchUser(user.id); + mIActivityManager.switchUser(user.id); } catch (RemoteException re) { Log.e(TAG, "Couldn't switch user " + re); } @@ -932,7 +938,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, /** {@inheritDoc} */ public void onShow(DialogInterface dialog) { - MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU); + mMetricsLogger.visible(MetricsEvent.POWER_MENU); } /** @@ -1148,6 +1154,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } protected int getActionLayoutId(Context context) { + if (isControlsEnabled(context)) { + return com.android.systemui.R.layout.global_actions_grid_item_v2; + } return com.android.systemui.R.layout.global_actions_grid_item; } @@ -1160,13 +1169,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, TextView messageView = (TextView) v.findViewById(R.id.message); messageView.setSelected(true); // necessary for marquee to work - TextView statusView = (TextView) v.findViewById(R.id.status); - final String status = getStatus(); - if (!TextUtils.isEmpty(status)) { - statusView.setText(status); - } else { - statusView.setVisibility(View.GONE); - } if (mIcon != null) { icon.setImageDrawable(mIcon); icon.setScaleType(ScaleType.CENTER_CROP); @@ -1251,32 +1253,26 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, LayoutInflater inflater) { willCreate(); - View v = inflater.inflate(R - .layout.global_actions_item, parent, false); + View v = inflater.inflate(com.android.systemui.R + .layout.global_actions_grid_item, parent, false); ImageView icon = (ImageView) v.findViewById(R.id.icon); TextView messageView = (TextView) v.findViewById(R.id.message); - TextView statusView = (TextView) v.findViewById(R.id.status); final boolean enabled = isEnabled(); + boolean on = ((mState == State.On) || (mState == State.TurningOn)); if (messageView != null) { - messageView.setText(mMessageResId); + messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); messageView.setEnabled(enabled); messageView.setSelected(true); // necessary for marquee to work } - boolean on = ((mState == State.On) || (mState == State.TurningOn)); if (icon != null) { icon.setImageDrawable(context.getDrawable( (on ? mEnabledIconResId : mDisabledIconResid))); icon.setEnabled(enabled); } - if (statusView != null) { - statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); - statusView.setVisibility(View.VISIBLE); - statusView.setEnabled(enabled); - } v.setEnabled(enabled); return v; @@ -1422,8 +1418,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { // Airplane mode can be changed after ECM exits if airplane toggle button // is pressed during ECM mode - if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && - mIsWaitingForEcmExit) { + if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) + && mIsWaitingForEcmExit) { mIsWaitingForEcmExit = false; changeAirplaneModeSystemSetting(true); } @@ -1492,7 +1488,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (mHasTelephony) return; boolean airplaneModeOn = Settings.Global.getInt( - mContext.getContentResolver(), + mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) == 1; mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; @@ -1504,7 +1500,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, */ private void changeAirplaneModeSystemSetting(boolean on) { Settings.Global.putInt( - mContext.getContentResolver(), + mContentResolver, Settings.Global.AIRPLANE_MODE_ON, on ? 1 : 0); Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); @@ -1533,15 +1529,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private ResetOrientationData mResetOrientationData; private boolean mHadTopUi; private final StatusBarWindowController mStatusBarWindowController; + private boolean mControlsEnabled; ActionsDialog(Context context, MyAdapter adapter, - GlobalActionsPanelPlugin.PanelViewController plugin) { + GlobalActionsPanelPlugin.PanelViewController plugin, + boolean controlsEnabled) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; mColorExtractor = Dependency.get(SysuiColorExtractor.class); mStatusBarService = Dependency.get(IStatusBarService.class); mStatusBarWindowController = Dependency.get(StatusBarWindowController.class); + mControlsEnabled = controlsEnabled; // Window initialization Window window = getWindow(); @@ -1658,6 +1657,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private int getGlobalActionsLayoutId(Context context) { + if (mControlsEnabled) { + return com.android.systemui.R.layout.global_actions_grid_v2; + } + int rotation = RotationUtils.getRotation(context); boolean useGridLayout = isForceGridEnabled(context) || (shouldUsePanel() && rotation == RotationUtils.ROTATION_NONE); @@ -1861,4 +1864,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private static boolean shouldUseSeparatedView() { return true; } + + private static boolean isControlsEnabled(Context context) { + return Settings.Secure.getInt( + context.getContentResolver(), "systemui.controls_available", 0) == 1; + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java new file mode 100644 index 000000000000..6749f1d663eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Single row implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsFlatLayout extends GlobalActionsLayout { + public GlobalActionsFlatLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mBackgroundsSet = true; + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // backgrounds set only once, the first time onMeasure is called after inflation + // if (getListView() != null && !mBackgroundsSet) { + // setBackgrounds(); + // mBackgroundsSet = true; + // } + } + + @VisibleForTesting + protected void setupListView() { + ListGridLayout listView = getListView(); + listView.setExpectedCount(Math.min(2, mAdapter.countListItems())); + listView.setReverseSublists(shouldReverseSublists()); + listView.setReverseItems(shouldReverseListItems()); + listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns()); + } + + @Override + public void onUpdateList() { + setupListView(); + super.onUpdateList(); + updateSeparatedItemSize(); + } + + /** + * If the separated view contains only one item, expand the bounds of that item to take up the + * entire view, so that the whole thing is touch-able. + */ + @VisibleForTesting + protected void updateSeparatedItemSize() { + ViewGroup separated = getSeparatedView(); + if (separated.getChildCount() == 0) { + return; + } + View firstChild = separated.getChildAt(0); + ViewGroup.LayoutParams childParams = firstChild.getLayoutParams(); + + if (separated.getChildCount() == 1) { + childParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + childParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + } else { + childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; + childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + } + } + + @Override + protected ListGridLayout getListView() { + return (ListGridLayout) super.getListView(); + } + + @Override + protected void removeAllListViews() { + ListGridLayout list = getListView(); + if (list != null) { + list.removeAllItems(); + } + } + + @Override + protected void addToListView(View v, boolean reverse) { + ListGridLayout list = getListView(); + if (list != null) { + list.addItem(v); + } + } + + @Override + public void removeAllItems() { + ViewGroup separatedList = getSeparatedView(); + ListGridLayout list = getListView(); + if (separatedList != null) { + separatedList.removeAllViews(); + } + if (list != null) { + list.removeAllItems(); + } + } + + /** + * Determines whether the ListGridLayout should fill sublists in the reverse order. + * Used to account for sublist ordering changing between landscape and seascape views. + */ + @VisibleForTesting + protected boolean shouldReverseSublists() { + if (getCurrentRotation() == ROTATION_SEASCAPE) { + return true; + } + return false; + } + + /** + * Determines whether the ListGridLayout should fill rows first instead of columns. + * Used to account for vertical/horizontal changes due to landscape or seascape rotations. + */ + @VisibleForTesting + protected boolean shouldSwapRowsAndColumns() { + if (getCurrentRotation() == ROTATION_NONE) { + return false; + } + return true; + } + + @Override + protected boolean shouldReverseListItems() { + int rotation = getCurrentRotation(); + boolean reverse = false; // should we add items to parents in the reverse order? + if (rotation == ROTATION_NONE + || rotation == ROTATION_SEASCAPE) { + reverse = !reverse; // if we're in portrait or seascape, reverse items + } + if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + reverse = !reverse; // if we're in an RTL language, reverse items (again) + } + return reverse; + } + + @VisibleForTesting + protected float getAnimationDistance() { + int rows = getListView().getRowCount(); + float gridItemSize = getContext().getResources().getDimension( + com.android.systemui.R.dimen.global_actions_grid_item_height); + return rows * gridItemSize / 2; + } + + @Override + public float getAnimationOffsetX() { + switch (getCurrentRotation()) { + case ROTATION_LANDSCAPE: + return getAnimationDistance(); + case ROTATION_SEASCAPE: + return -getAnimationDistance(); + default: // Portrait + return 0; + } + } + + @Override + public float getAnimationOffsetY() { + if (getCurrentRotation() == ROTATION_NONE) { + return getAnimationDistance(); + } + return 0; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index d385123117d3..c911bf28effd 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -45,24 +45,32 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import javax.inject.Inject; + +import dagger.Lazy; + public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, PluginListener<GlobalActionsPanelPlugin> { private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f; private final Context mContext; + private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy; private final KeyguardStateController mKeyguardStateController; private final DeviceProvisionedController mDeviceProvisionedController; private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension; private GlobalActionsPanelPlugin mPlugin; private final CommandQueue mCommandQueue; - private GlobalActionsDialog mGlobalActions; + private GlobalActionsDialog mGlobalActionsDialog; private boolean mDisabled; private final PluginManager mPluginManager; private final String mPluginPackageName; - public GlobalActionsImpl(Context context, CommandQueue commandQueue) { + @Inject + public GlobalActionsImpl(Context context, CommandQueue commandQueue, + Lazy<GlobalActionsDialog> globalActionsDialogLazy) { mContext = context; + mGlobalActionsDialogLazy = globalActionsDialogLazy; mKeyguardStateController = Dependency.get(KeyguardStateController.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mPluginManager = Dependency.get(PluginManager.class); @@ -83,19 +91,17 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mCommandQueue.removeCallback(this); mPluginManager.removePluginListener(this); if (mPlugin != null) mPlugin.onDestroy(); - if (mGlobalActions != null) { - mGlobalActions.destroy(); - mGlobalActions = null; + if (mGlobalActionsDialog != null) { + mGlobalActionsDialog.destroy(); + mGlobalActionsDialog = null; } } @Override public void showGlobalActions(GlobalActionsManager manager) { if (mDisabled) return; - if (mGlobalActions == null) { - mGlobalActions = new GlobalActionsDialog(mContext, manager); - } - mGlobalActions.showDialog(mKeyguardStateController.isShowing(), + mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); + mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(), mDeviceProvisionedController.isDeviceProvisioned(), mPlugin != null ? mPlugin : mPanelExtension.get()); Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth(); @@ -189,8 +195,8 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0; if (displayId != mContext.getDisplayId() || disabled == mDisabled) return; mDisabled = disabled; - if (disabled && mGlobalActions != null) { - mGlobalActions.dismissDialog(); + if (disabled && mGlobalActionsDialog != null) { + mGlobalActionsDialog.dismissDialog(); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 02c4beb7ac1b..9f2bbc680897 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -20,7 +20,7 @@ import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED; import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; import android.animation.Animator; @@ -246,20 +246,13 @@ public class GlobalScreenshot { mSaveInBgTask.cancel(false); } - if (!DeviceConfig.getBoolean( - NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) { - mNotificationsController.reset(); - mNotificationsController.setImage(mScreenBitmap); - mNotificationsController.showSavingScreenshotNotification(); - } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute(); } /** * Takes a screenshot of the current display and shows an animation. */ - private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, - boolean navBarVisible, Rect crop) { + private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); @@ -278,21 +271,20 @@ public class GlobalScreenshot { mScreenBitmap.prepareToDraw(); // Start the post-screenshot animation - startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, - statusBarVisible, navBarVisible); + startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels); } - void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) { + void takeScreenshot(Consumer<Uri> finisher) { mDisplay.getRealMetrics(mDisplayMetrics); - takeScreenshot(finisher, statusBarVisible, navBarVisible, + takeScreenshot( + finisher, new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } /** * Displays a screenshot selector */ - void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible, - final boolean navBarVisible) { + void takeScreenshotPartial(final Consumer<Uri> finisher) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { @Override @@ -312,8 +304,7 @@ public class GlobalScreenshot { if (rect != null) { if (rect.width() != 0 && rect.height() != 0) { // Need mScreenshotLayout to handle it after the view disappears - mScreenshotLayout.post(() -> takeScreenshot( - finisher, statusBarVisible, navBarVisible, rect)); + mScreenshotLayout.post(() -> takeScreenshot(finisher, rect)); } } @@ -364,8 +355,7 @@ public class GlobalScreenshot { /** * Starts the animation after taking the screenshot */ - private void startAnimation(final Consumer<Uri> finisher, int w, int h, - boolean statusBarVisible, boolean navBarVisible) { + private void startAnimation(final Consumer<Uri> finisher, int w, int h) { // If power save is on, show a toast so there is some visual indication that a screenshot // has been taken. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -385,50 +375,30 @@ public class GlobalScreenshot { mScreenshotAnimation.removeAllListeners(); } - boolean useCornerFlow = - DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false); mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); - ValueAnimator screenshotFadeOutAnim = useCornerFlow - ? createScreenshotToCornerAnimation(w, h) - : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); + ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h); mScreenshotAnimation = new AnimatorSet(); mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // Save the screenshot once we have a bit of time now - if (!useCornerFlow) { - saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(Uri uri, List<Notification.Action> actions) { - if (uri == null) { - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - } else { - mNotificationsController - .showScreenshotActionsNotification(uri, actions); - } - } - }); - clearScreenshot(); - } else { - saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(Uri uri, List<Notification.Action> actions) { - if (uri == null) { - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - } else { - mScreenshotHandler.post(() -> - createScreenshotActionsShadeAnimation(actions).start()); - } + saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { + @Override + void onActionsReady(Uri uri, List<Notification.Action> actions) { + if (uri == null) { + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + } else { + mScreenshotHandler.post(() -> + createScreenshotActionsShadeAnimation(actions).start()); } - }); - mScreenshotHandler.sendMessageDelayed( - mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), - SCREENSHOT_CORNER_TIMEOUT_MILLIS); - } + } + }); + mScreenshotHandler.sendMessageDelayed( + mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), + SCREENSHOT_CORNER_TIMEOUT_MILLIS); } }); mScreenshotHandler.post(() -> { @@ -492,7 +462,7 @@ public class GlobalScreenshot { } @Override - public void onAnimationEnd(android.animation.Animator animation) { + public void onAnimationEnd(Animator animation) { mScreenshotFlash.setVisibility(View.GONE); } }); @@ -513,81 +483,6 @@ public class GlobalScreenshot { return anim; } - private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, - boolean navBarVisible) { - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mBackgroundView.setVisibility(View.GONE); - mScreenshotView.setVisibility(View.GONE); - mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); - } - }); - - if (!statusBarVisible || !navBarVisible) { - // There is no status bar/nav bar, so just fade the screenshot away in place - anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - - t * (SCREENSHOT_DROP_IN_MIN_SCALE - - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE); - mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(1f - t); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - } - }); - } else { - // In the case where there is a status bar, animate to the origin of the bar (top-left) - final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION - / SCREENSHOT_DROP_OUT_DURATION; - final Interpolator scaleInterpolator = new Interpolator() { - @Override - public float getInterpolation(float x) { - if (x < scaleDurationPct) { - // Decelerate, and scale the input accordingly - return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f)); - } - return 1f; - } - }; - - // Determine the bounds of how to scale - float halfScreenWidth = (w - 2f * mBgPadding) / 2f; - float halfScreenHeight = (h - 2f * mBgPadding) / 2f; - final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET; - final PointF finalPos = new PointF( - -halfScreenWidth - + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth, - -halfScreenHeight - + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight); - - // Animate the screenshot to the status bar - anim.setDuration(SCREENSHOT_DROP_OUT_DURATION); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - - scaleInterpolator.getInterpolation(t) - * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE); - mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t)); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - mScreenshotView.setTranslationX(t * finalPos.x); - mScreenshotView.setTranslationY(t * finalPos.y); - } - }); - } - return anim; - } - private ValueAnimator createScreenshotToCornerAnimation(int w, int h) { ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); @@ -648,13 +543,16 @@ public class GlobalScreenshot { }); mActionsView.addView(actionChip); } - TextView scrollChip = (TextView) inflater.inflate( - R.layout.global_screenshot_action_chip, mActionsView, false); - Toast scrollNotImplemented = Toast.makeText( - mContext, "Not implemented", Toast.LENGTH_SHORT); - scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate - scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); - mActionsView.addView(scrollChip); + + if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) { + TextView scrollChip = (TextView) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + Toast scrollNotImplemented = Toast.makeText( + mContext, "Not implemented", Toast.LENGTH_SHORT); + scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate + scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); + mActionsView.addView(scrollChip); + } ValueAnimator animator = ValueAnimator.ofFloat(0, 1); mActionsView.setY(mDisplayMetrics.heightPixels); @@ -776,8 +674,7 @@ public class GlobalScreenshot { String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE); Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent); ActivityOptions opts = ActivityOptions.makeBasic(); - context.startActivityAsUser(actionIntent, opts.toBundle(), - UserHandle.CURRENT); + context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT); ScreenshotSmartActions.notifyScreenshotAction( context, intent.getStringExtra(EXTRA_ID), actionType, true); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java new file mode 100644 index 000000000000..11aa80bbea35 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.Nullable; +import android.app.Notification; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PointF; +import android.graphics.Rect; +import android.media.MediaActionSound; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.PowerManager; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.Interpolator; +import android.widget.ImageView; +import android.widget.Toast; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; + +import java.util.List; +import java.util.function.Consumer; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Class for handling device screen shots + * + * @deprecated will be removed when corner flow is complete and tested + */ +@Singleton +@Deprecated +public class GlobalScreenshotLegacy { + + // These strings are used for communicating the action invoked to + // ScreenshotNotificationSmartActionsProvider. + static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; + static final String EXTRA_ID = "android:screenshot_id"; + static final String ACTION_TYPE_DELETE = "Delete"; + static final String ACTION_TYPE_SHARE = "Share"; + static final String ACTION_TYPE_EDIT = "Edit"; + static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; + static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; + + static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; + static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; + static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; + + private static final String TAG = "GlobalScreenshot"; + + private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130; + private static final int SCREENSHOT_DROP_IN_DURATION = 430; + private static final int SCREENSHOT_DROP_OUT_DELAY = 500; + private static final int SCREENSHOT_DROP_OUT_DURATION = 430; + private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370; + private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320; + private static final float BACKGROUND_ALPHA = 0.5f; + private static final float SCREENSHOT_SCALE = 1f; + private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f; + private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f; + private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f; + private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f; + + private final ScreenshotNotificationsController mNotificationsController; + + private Context mContext; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowLayoutParams; + private Display mDisplay; + private DisplayMetrics mDisplayMetrics; + + private Bitmap mScreenBitmap; + private View mScreenshotLayout; + private ScreenshotSelectorView mScreenshotSelectorView; + private ImageView mBackgroundView; + private ImageView mScreenshotView; + private ImageView mScreenshotFlash; + + private AnimatorSet mScreenshotAnimation; + + private float mBgPadding; + private float mBgPaddingScale; + + private AsyncTask<Void, Void, Void> mSaveInBgTask; + + private MediaActionSound mCameraSound; + + /** + * @param context everything needs a context :( + */ + @Inject + public GlobalScreenshotLegacy( + Context context, @Main Resources resources, LayoutInflater layoutInflater, + ScreenshotNotificationsController screenshotNotificationsController) { + mContext = context; + mNotificationsController = screenshotNotificationsController; + + // Inflate the screenshot layout + mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null); + mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background); + mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy); + + mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash); + mScreenshotSelectorView = mScreenshotLayout.findViewById( + R.id.global_screenshot_legacy_selector); + mScreenshotLayout.setFocusable(true); + mScreenshotSelectorView.setFocusable(true); + mScreenshotSelectorView.setFocusableInTouchMode(true); + mScreenshotLayout.setOnTouchListener((v, event) -> { + // Intercept and ignore all touch events + return true; + }); + + // Setup the window that we are going to use + mWindowLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, + WindowManager.LayoutParams.TYPE_SCREENSHOT, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, + PixelFormat.TRANSLUCENT); + mWindowLayoutParams.setTitle("ScreenshotAnimation"); + mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mDisplay = mWindowManager.getDefaultDisplay(); + mDisplayMetrics = new DisplayMetrics(); + mDisplay.getRealMetrics(mDisplayMetrics); + + // Scale has to account for both sides of the bg + mBgPadding = (float) resources.getDimensionPixelSize( + R.dimen.global_screenshot_legacy_bg_padding); + mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; + + + // Setup the Camera shutter sound + mCameraSound = new MediaActionSound(); + mCameraSound.load(MediaActionSound.SHUTTER_CLICK); + } + + /** + * Creates a new worker thread and saves the screenshot to the media store. + */ + private void saveScreenshotInWorkerThread( + Consumer<Uri> finisher, + @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) { + GlobalScreenshot.SaveImageInBackgroundData data = + new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = mScreenBitmap; + data.finisher = finisher; + data.mActionsReadyListener = actionsReadyListener; + if (mSaveInBgTask != null) { + mSaveInBgTask.cancel(false); + } + + mNotificationsController.reset(); + mNotificationsController.setImage(mScreenBitmap); + mNotificationsController.showSavingScreenshotNotification(); + + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute(); + } + + /** + * Takes a screenshot of the current display and shows an animation. + */ + private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, + boolean navBarVisible, Rect crop) { + int rot = mDisplay.getRotation(); + int width = crop.width(); + int height = crop.height(); + + // Take the screenshot + mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot); + if (mScreenBitmap == null) { + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + finisher.accept(null); + return; + } + + // Optimizations + mScreenBitmap.setHasAlpha(false); + mScreenBitmap.prepareToDraw(); + + // Start the post-screenshot animation + startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + statusBarVisible, navBarVisible); + } + + void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) { + mDisplay.getRealMetrics(mDisplayMetrics); + takeScreenshot(finisher, statusBarVisible, navBarVisible, + new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); + } + + /** + * Displays a screenshot selector + */ + void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible, + final boolean navBarVisible) { + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + ScreenshotSelectorView view = (ScreenshotSelectorView) v; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + view.startSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_MOVE: + view.updateSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_UP: + view.setVisibility(View.GONE); + mWindowManager.removeView(mScreenshotLayout); + final Rect rect = view.getSelectionRect(); + if (rect != null) { + if (rect.width() != 0 && rect.height() != 0) { + // Need mScreenshotLayout to handle it after the view disappears + mScreenshotLayout.post(() -> takeScreenshot( + finisher, statusBarVisible, navBarVisible, rect)); + } + } + + view.stopSelection(); + return true; + } + + return false; + } + }); + mScreenshotLayout.post(new Runnable() { + @Override + public void run() { + mScreenshotSelectorView.setVisibility(View.VISIBLE); + mScreenshotSelectorView.requestFocus(); + } + }); + } + + /** + * Cancels screenshot request + */ + void stopScreenshot() { + // If the selector layer still presents on screen, we remove it and resets its state. + if (mScreenshotSelectorView.getSelectionRect() != null) { + mWindowManager.removeView(mScreenshotLayout); + mScreenshotSelectorView.stopSelection(); + } + } + + /** + * Clears current screenshot + */ + private void clearScreenshot() { + if (mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.removeView(mScreenshotLayout); + } + + // Clear any references to the bitmap + mScreenBitmap = null; + mScreenshotView.setImageBitmap(null); + mBackgroundView.setVisibility(View.GONE); + mScreenshotView.setVisibility(View.GONE); + mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); + } + + /** + * Starts the animation after taking the screenshot + */ + private void startAnimation(final Consumer<Uri> finisher, int w, int h, + boolean statusBarVisible, boolean navBarVisible) { + // If power save is on, show a toast so there is some visual indication that a screenshot + // has been taken. + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + if (powerManager.isPowerSaveMode()) { + Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); + } + + // Add the view for the animation + mScreenshotView.setImageBitmap(mScreenBitmap); + mScreenshotLayout.requestFocus(); + + // Setup the animation with the screenshot just taken + if (mScreenshotAnimation != null) { + if (mScreenshotAnimation.isStarted()) { + mScreenshotAnimation.end(); + } + mScreenshotAnimation.removeAllListeners(); + } + + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); + ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h, + statusBarVisible, navBarVisible); + mScreenshotAnimation = new AnimatorSet(); + mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); + mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Save the screenshot once we have a bit of time now + saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() { + @Override + void onActionsReady(Uri uri, List<Notification.Action> actions) { + if (uri == null) { + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + } else { + mNotificationsController.showScreenshotActionsNotification( + uri, actions); + } + } + }); + clearScreenshot(); + } + }); + mScreenshotLayout.post(() -> { + // Play the shutter sound to notify that we've taken a screenshot + mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + + mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mScreenshotView.buildLayer(); + mScreenshotAnimation.start(); + }); + } + + private ValueAnimator createScreenshotDropInAnimation() { + final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) + / SCREENSHOT_DROP_IN_DURATION); + final float flashDurationPct = 2f * flashPeakDurationPct; + final Interpolator flashAlphaInterpolator = new Interpolator() { + @Override + public float getInterpolation(float x) { + // Flash the flash view in and out quickly + if (x <= flashDurationPct) { + return (float) Math.sin(Math.PI * (x / flashDurationPct)); + } + return 0; + } + }; + final Interpolator scaleInterpolator = new Interpolator() { + @Override + public float getInterpolation(float x) { + // We start scaling when the flash is at it's peak + if (x < flashPeakDurationPct) { + return 0; + } + return (x - flashDurationPct) / (1f - flashDurationPct); + } + }; + + Resources r = mContext.getResources(); + if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES) { + mScreenshotView.getBackground().setTint(Color.BLACK); + } else { + mScreenshotView.getBackground().setTintList(null); + } + + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(SCREENSHOT_DROP_IN_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBackgroundView.setAlpha(0f); + mBackgroundView.setVisibility(View.VISIBLE); + mScreenshotView.setAlpha(0f); + mScreenshotView.setTranslationX(0f); + mScreenshotView.setTranslationY(0f); + mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale); + mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale); + mScreenshotView.setVisibility(View.VISIBLE); + mScreenshotFlash.setAlpha(0f); + mScreenshotFlash.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(android.animation.Animator animation) { + mScreenshotFlash.setVisibility(View.GONE); + } + }); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (Float) animation.getAnimatedValue(); + float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale) + - scaleInterpolator.getInterpolation(t) + * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE); + mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(t); + mScreenshotView.setScaleX(scaleT); + mScreenshotView.setScaleY(scaleT); + mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t)); + } + }); + return anim; + } + + private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, + boolean navBarVisible) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBackgroundView.setVisibility(View.GONE); + mScreenshotView.setVisibility(View.GONE); + mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); + } + }); + + if (!statusBarVisible || !navBarVisible) { + // There is no status bar/nav bar, so just fade the screenshot away in place + anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (Float) animation.getAnimatedValue(); + float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) + - t * (SCREENSHOT_DROP_IN_MIN_SCALE + - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE); + mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(1f - t); + mScreenshotView.setScaleX(scaleT); + mScreenshotView.setScaleY(scaleT); + } + }); + } else { + // In the case where there is a status bar, animate to the origin of the bar (top-left) + final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION + / SCREENSHOT_DROP_OUT_DURATION; + final Interpolator scaleInterpolator = new Interpolator() { + @Override + public float getInterpolation(float x) { + if (x < scaleDurationPct) { + // Decelerate, and scale the input accordingly + return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f)); + } + return 1f; + } + }; + + // Determine the bounds of how to scale + float halfScreenWidth = (w - 2f * mBgPadding) / 2f; + float halfScreenHeight = (h - 2f * mBgPadding) / 2f; + final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET; + final PointF finalPos = new PointF( + -halfScreenWidth + + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth, + -halfScreenHeight + + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight); + + // Animate the screenshot to the status bar + anim.setDuration(SCREENSHOT_DROP_OUT_DURATION); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (Float) animation.getAnimatedValue(); + float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) + - scaleInterpolator.getInterpolation(t) + * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE); + mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t)); + mScreenshotView.setScaleX(scaleT); + mScreenshotView.setScaleY(scaleT); + mScreenshotView.setTranslationX(t * finalPos.x); + mScreenshotView.setTranslationY(t * finalPos.y); + } + }); + } + return anim; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 29b96a90a734..4f045d5c1b71 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -16,15 +16,21 @@ package com.android.systemui.screenshot; +import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW; + import android.app.Service; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.os.UserManager; +import android.provider.DeviceConfig; import android.util.Log; import android.view.WindowManager; @@ -36,9 +42,10 @@ public class TakeScreenshotService extends Service { private static final String TAG = "TakeScreenshotService"; private final GlobalScreenshot mScreenshot; + private final GlobalScreenshotLegacy mScreenshotLegacy; private final UserManager mUserManager; - private Handler mHandler = new Handler() { + private Handler mHandler = new Handler(Looper.myLooper()) { @Override public void handleMessage(Message msg) { final Messenger callback = msg.replyTo; @@ -59,12 +66,24 @@ public class TakeScreenshotService extends Service { return; } + // TODO (mkephart): clean up once notifications flow is fully deprecated + boolean useCornerFlow = DeviceConfig.getBoolean( + NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false); switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: - mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + if (useCornerFlow) { + mScreenshot.takeScreenshot(finisher); + } else { + mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + } break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: - mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0); + if (useCornerFlow) { + mScreenshot.takeScreenshotPartial(finisher); + } else { + mScreenshotLegacy.takeScreenshotPartial( + finisher, msg.arg1 > 0, msg.arg2 > 0); + } break; default: Log.d(TAG, "Invalid screenshot option: " + msg.what); @@ -73,8 +92,10 @@ public class TakeScreenshotService extends Service { }; @Inject - public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) { + public TakeScreenshotService(GlobalScreenshot globalScreenshot, + GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) { mScreenshot = globalScreenshot; + mScreenshotLegacy = globalScreenshotLegacy; mUserManager = userManager; } @@ -86,6 +107,7 @@ public class TakeScreenshotService extends Service { @Override public boolean onUnbind(Intent intent) { if (mScreenshot != null) mScreenshot.stopScreenshot(); + if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot(); return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 43d0399c6d62..667e721ae37d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -584,7 +584,15 @@ public class NotificationRemoteInputManager implements Dumpable { public void bindRow(ExpandableNotificationRow row) { row.setRemoteInputController(mRemoteInputController); - row.setRemoteViewClickHandler(mOnClickHandler); + } + + /** + * Return on-click handler for notification remote views + * + * @return on-click handler + */ + public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() { + return mOnClickHandler; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java index d1f6ebf3826d..ec8dbead7de2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar; import android.content.Context; +import com.android.systemui.statusbar.notification.row.NotificationRowModule; + import javax.inject.Singleton; import dagger.Module; @@ -26,7 +28,7 @@ import dagger.Provides; /** * Dagger Module providing common dependencies of StatusBar. */ -@Module +@Module(includes = {NotificationRowModule.class}) public class StatusBarDependenciesModule { /** * Provides our instance of CommandQueue which is considered optional. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index dc84b5785ad9..9c626f7b877d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -307,6 +307,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi case Icon.TYPE_RESOURCE: return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId(); case Icon.TYPE_URI: + case Icon.TYPE_URI_ADAPTIVE_BITMAP: return a.getUriString().equals(b.getUriString()); default: return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index a2578ab3cfa6..4a2283171694 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -32,7 +32,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; @@ -44,7 +43,7 @@ import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.logging.NotifEvent; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -68,6 +67,8 @@ import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; +import dagger.Lazy; + /** * NotificationEntryManager is responsible for the adding, removing, and updating of * {@link NotificationEntry}s. It also handles tasks such as their inflation and their interaction @@ -126,8 +127,9 @@ public class NotificationEntryManager implements new ArrayMap<>(); // Lazily retrieved dependencies - private NotificationRemoteInputManager mRemoteInputManager; - private NotificationRowBinder mNotificationRowBinder; + private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy; + private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy; + private final LeakDetector mLeakDetector; private final KeyguardEnvironment mKeyguardEnvironment; private final NotificationGroupManager mGroupManager; @@ -173,12 +175,18 @@ public class NotificationEntryManager implements NotificationGroupManager groupManager, NotificationRankingManager rankingManager, KeyguardEnvironment keyguardEnvironment, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + Lazy<NotificationRowBinder> notificationRowBinderLazy, + Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, + LeakDetector leakDetector) { mNotifLog = notifLog; mGroupManager = groupManager; mRankingManager = rankingManager; mKeyguardEnvironment = keyguardEnvironment; mFeatureFlags = featureFlags; + mNotificationRowBinderLazy = notificationRowBinderLazy; + mRemoteInputManagerLazy = notificationRemoteInputManagerLazy; + mLeakDetector = leakDetector; } /** Once called, the NEM will start processing notification events from system server. */ @@ -204,20 +212,6 @@ public class NotificationEntryManager implements mRemoveInterceptor = interceptor; } - /** - * Our dependencies can have cyclic references, so some need to be lazy - */ - private NotificationRemoteInputManager getRemoteInputManager() { - if (mRemoteInputManager == null) { - mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); - } - return mRemoteInputManager; - } - - public void setRowBinder(NotificationRowBinder notificationRowBinder) { - mNotificationRowBinder = notificationRowBinder; - } - public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, HeadsUpManager headsUpManager) { @@ -468,7 +462,7 @@ public class NotificationEntryManager implements handleGroupSummaryRemoved(key); removeVisibleNotification(key); updateNotifications("removeNotificationInternal"); - Dependency.get(LeakDetector.class).trackGarbage(entry); + mLeakDetector.trackGarbage(entry); removedByUser |= entryDismissed; mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(), @@ -507,8 +501,8 @@ public class NotificationEntryManager implements boolean isForeground = (entry.getSbn().getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; boolean keepForReply = - getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry) - || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry); + mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry) + || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry); if (isForeground || keepForReply) { // the child is a foreground service notification which we can't remove or it's // a child we're keeping around for reply! @@ -536,12 +530,13 @@ public class NotificationEntryManager implements NotificationEntry entry = new NotificationEntry(notification, ranking); - Dependency.get(LeakDetector.class).trackInstance(entry); + mLeakDetector.trackInstance(entry); // Construct the expanded view. if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - requireBinder().inflateViews(entry, () -> performRemoveNotification(notification, - REASON_CANCEL)); + mNotificationRowBinderLazy.get() + .inflateViews(entry, () -> performRemoveNotification(notification, + REASON_CANCEL)); } abortExistingInflation(key, "addNotification"); @@ -586,15 +581,16 @@ public class NotificationEntryManager implements } if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - requireBinder().inflateViews(entry, () -> performRemoveNotification(notification, - REASON_CANCEL)); + mNotificationRowBinderLazy.get() + .inflateViews(entry, () -> performRemoveNotification(notification, + REASON_CANCEL)); } updateNotifications("updateNotificationInternal"); if (DEBUG) { // Is this for you? - boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class) + boolean isForCurrentUser = mKeyguardEnvironment .isNotificationForCurrentProfiles(notification); Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); } @@ -644,11 +640,12 @@ public class NotificationEntryManager implements // By comparing the old and new UI adjustments, reinflate the view accordingly. for (NotificationEntry entry : entries) { - requireBinder().onNotificationRankingUpdated( - entry, - oldImportances.get(entry.getKey()), - oldAdjustments.get(entry.getKey()), - NotificationUiAdjustment.extractFromNotificationEntry(entry)); + mNotificationRowBinderLazy.get() + .onNotificationRankingUpdated( + entry, + oldImportances.get(entry.getKey()), + oldAdjustments.get(entry.getKey()), + NotificationUiAdjustment.extractFromNotificationEntry(entry)); } updateNotifications("updateNotificationRanking"); @@ -728,14 +725,6 @@ public class NotificationEntryManager implements } } - private NotificationRowBinder requireBinder() { - if (mNotificationRowBinder == null) { - throw new RuntimeException("You must initialize NotificationEntryManager by calling" - + "setRowBinder() before using."); - } - return mNotificationRowBinder; - } - /* * ----- * Annexed from NotificationData below: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index e1268f6d60ef..73bfe2536830 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -16,13 +16,11 @@ package com.android.systemui.statusbar.notification.collection; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; - import java.util.List; /** - * Utility class for dumping the results of a {@link NotifListBuilder} to a debug string. + * Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string. */ public class ListDumper { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 856b75b7e36c..4b15b7fbce5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -47,9 +47,13 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; import java.lang.annotation.Retention; @@ -123,36 +127,25 @@ public class NotifCollection { * Sets the class responsible for converting the collection into the list of currently-visible * notifications. */ - public void setBuildListener(CollectionReadyForBuildListener buildListener) { + void setBuildListener(CollectionReadyForBuildListener buildListener) { Assert.isMainThread(); mBuildListener = buildListener; } - /** - * Returns the list of "active" notifications, i.e. the notifications that are currently posted - * to the phone. In general, this tracks closely to the list maintained by NotificationManager, - * but it can diverge slightly due to lifetime extenders. - * - * The returned list is read-only, unsorted, unfiltered, and ungrouped. - */ - public Collection<NotificationEntry> getNotifs() { + /** @see NotifPipeline#getActiveNotifs() */ + Collection<NotificationEntry> getActiveNotifs() { Assert.isMainThread(); return mReadOnlyNotificationSet; } - /** - * Registers a listener to be informed when notifications are added, removed or updated. - */ - public void addCollectionListener(NotifCollectionListener listener) { + /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */ + void addCollectionListener(NotifCollectionListener listener) { Assert.isMainThread(); mNotifCollectionListeners.add(listener); } - /** - * Registers a lifetime extender. Lifetime extenders can cause notifications that have been - * dismissed or retracted to be temporarily retained in the collection. - */ - public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { + /** @see NotifPipeline#addNotificationLifetimeExtender(NotifLifetimeExtender) */ + void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { Assert.isMainThread(); checkForReentrantCall(); if (mLifetimeExtenders.contains(extender)) { @@ -165,7 +158,7 @@ public class NotifCollection { /** * Dismiss a notification on behalf of the user. */ - public void dismissNotification( + void dismissNotification( NotificationEntry entry, @CancellationReason int reason, @NonNull DismissedByUserStats stats) { @@ -446,7 +439,7 @@ public class NotifCollection { REASON_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) - @interface CancellationReason {} + public @interface CancellationReason {} public static final int REASON_UNKNOWN = 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index 0d175574f16b..e7b772f1c7b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -25,6 +25,9 @@ import android.service.notification.StatusBarNotification; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; @@ -107,7 +110,7 @@ public class NotifInflaterImpl implements NotifInflater { DISMISS_SENTIMENT_NEUTRAL, NotificationVisibility.obtain(entry.getKey(), entry.getRanking().getRank(), - mNotifCollection.getNotifs().size(), + mNotifCollection.getActiveNotifs().size(), true, NotificationLogger.getNotificationLocation(entry)) )); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java new file mode 100644 index 000000000000..71245178a876 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection; + +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; + +import java.util.Collection; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * The system that constructs the "shade list", the filtered, grouped, and sorted list of + * notifications that are currently being displayed to the user in the notification shade. + * + * The pipeline proceeds through a series of stages in order to produce the final list (see below). + * Each stage exposes hooks and listeners to allow other code to participate. + * + * This list differs from the canonical one we receive from system server in a few ways: + * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose + * views haven't been inflated yet. We also filter out some notifications if we're on the lock + * screen and notifications for other users. So participate, see + * {@link #addPreGroupFilter} and similar methods. + * - Grouped: Notifications that are part of the same group are clustered together into a single + * GroupEntry. These groups are then transformed in order to remove children or completely split + * them apart. To participate, see {@link #addPromoter}. + * - Sorted: All top-level notifications are sorted. To participate, see + * {@link #setSectionsProvider} and {@link #setComparators} + * + * The exact order of all hooks is as follows: + * 0. Collection listeners are fired ({@link #addCollectionListener}). + * 1. Pre-group filters are fired on each notification ({@link #addPreGroupFilter}). + * 2. Initial grouping is performed (NotificationEntries will have their parents set + * appropriately). + * 3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener}) + * 4. NotifPromoters are called on each notification with a parent ({@link #addPromoter}) + * 5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener}) + * 6. SectionsProvider is called on each top-level entry in the list ({@link #setSectionsProvider}) + * 7. Top-level entries within the same section are sorted by NotifComparators + * ({@link #setComparators}) + * 8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter}) + * 9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener}) + * 9. The list is handed off to the view layer to be rendered + */ +@Singleton +public class NotifPipeline { + private final NotifCollection mNotifCollection; + private final ShadeListBuilder mShadeListBuilder; + + @Inject + public NotifPipeline( + NotifCollection notifCollection, + ShadeListBuilder shadeListBuilder) { + mNotifCollection = notifCollection; + mShadeListBuilder = shadeListBuilder; + } + + /** + * Returns the list of "active" notifications, i.e. the notifications that are currently posted + * to the phone. In general, this tracks closely to the list maintained by NotificationManager, + * but it can diverge slightly due to lifetime extenders. + * + * The returned collection is read-only, unsorted, unfiltered, and ungrouped. + */ + public Collection<NotificationEntry> getActiveNotifs() { + return mNotifCollection.getActiveNotifs(); + } + + /** + * Registers a listener to be informed when notifications are added, removed or updated. + */ + public void addCollectionListener(NotifCollectionListener listener) { + mNotifCollection.addCollectionListener(listener); + } + + /** + * Registers a lifetime extender. Lifetime extenders can cause notifications that have been + * dismissed or retracted to be temporarily retained in the collection. + */ + public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { + mNotifCollection.addNotificationLifetimeExtender(extender); + } + + /** + * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters + * are called on each notification in the order that they were registered. If any filter + * returns true, the notification is removed from the pipeline (and no other filters are + * called on that notif). + */ + public void addPreGroupFilter(NotifFilter filter) { + mShadeListBuilder.addPreGroupFilter(filter); + } + + /** + * Called after notifications have been filtered and after the initial grouping has been + * performed but before NotifPromoters have had a chance to promote children out of groups. + */ + public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) { + mShadeListBuilder.addOnBeforeTransformGroupsListener(listener); + } + + /** + * Registers a promoter with the pipeline. Promoters are able to promote child notifications to + * top-level, i.e. move a notification that would be a child of a group and make it appear + * ungrouped. Promoters are called on each child notification in the order that they are + * registered. If any promoter returns true, the notification is removed from the group (and no + * other promoters are called on it). + */ + public void addPromoter(NotifPromoter promoter) { + mShadeListBuilder.addPromoter(promoter); + } + + /** + * Called after notifs have been filtered and groups have been determined but before sections + * have been determined or the notifs have been sorted. + */ + public void addOnBeforeSortListener(OnBeforeSortListener listener) { + mShadeListBuilder.addOnBeforeSortListener(listener); + } + + /** + * Assigns sections to each top-level entry, where a section is simply an integer. Sections are + * the primary metric by which top-level entries are sorted; NotifComparators are only consulted + * when two entries are in the same section. The pipeline doesn't assign any particular meaning + * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple + * numerical comparison. + */ + public void setSectionsProvider(SectionsProvider provider) { + mShadeListBuilder.setSectionsProvider(provider); + } + + /** + * Comparators that are used to sort top-level entries that share the same section. The + * comparators are executed in order until one of them returns a non-zero result. If all return + * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when). + */ + public void setComparators(List<NotifComparator> comparators) { + mShadeListBuilder.setComparators(comparators); + } + + /** + * Registers a filter with the pipeline to filter right before rendering the list (after + * pre-group filtering, grouping, promoting and sorting occurs). Filters are + * called on each notification in the order that they were registered. If any filter returns + * true, the notification is removed from the pipeline (and no other filters are called on that + * notif). + */ + public void addPreRenderFilter(NotifFilter filter) { + mShadeListBuilder.addPreRenderFilter(filter); + } + + /** + * Called at the end of the pipeline after the notif list has been finalized but before it has + * been handed off to the view layer. + */ + public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) { + mShadeListBuilder.addOnBeforeRenderListListener(listener); + } + + /** + * Returns a read-only view in to the current shade list, i.e. the list of notifications that + * are currently present in the shade. If this method is called during pipeline execution it + * will return the current state of the list, which will likely be only partially-generated. + */ + public List<ListEntry> getShadeList() { + return mShadeListBuilder.getShadeList(); + } +} 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 7301fe1df398..2fcfb8c811aa 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 @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 19d90f083592..76c524be1b8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -32,7 +32,6 @@ import android.annotation.MainThread; import android.annotation.Nullable; import android.util.ArrayMap; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; @@ -41,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.logging.NotifEvent; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.Assert; @@ -57,11 +57,13 @@ import javax.inject.Inject; import javax.inject.Singleton; /** - * The implementation of {@link NotifListBuilder}. + * The second half of {@link NotifPipeline}. Sits downstream of the NotifCollection and transforms + * its "notification set" into the "shade list", the filtered, grouped, and sorted list of + * notifications that are currently present in the notification shade. */ @MainThread @Singleton -public class NotifListBuilderImpl implements NotifListBuilder { +public class ShadeListBuilder { private final SystemClock mSystemClock; private final NotifLog mNotifLog; @@ -90,7 +92,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); @Inject - public NotifListBuilderImpl(SystemClock systemClock, NotifLog notifLog) { + public ShadeListBuilder(SystemClock systemClock, NotifLog notifLog) { Assert.isMainThread(); mSystemClock = systemClock; mNotifLog = notifLog; @@ -116,32 +118,28 @@ public class NotifListBuilderImpl implements NotifListBuilder { mOnRenderListListener = onRenderListListener; } - @Override - public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) { + void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); mOnBeforeTransformGroupsListeners.add(listener); } - @Override - public void addOnBeforeSortListener(OnBeforeSortListener listener) { + void addOnBeforeSortListener(OnBeforeSortListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); mOnBeforeSortListeners.add(listener); } - @Override - public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) { + void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); mOnBeforeRenderListListeners.add(listener); } - @Override - public void addPreGroupFilter(NotifFilter filter) { + void addPreGroupFilter(NotifFilter filter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -149,8 +147,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { filter.setInvalidationListener(this::onPreGroupFilterInvalidated); } - @Override - public void addPreRenderFilter(NotifFilter filter) { + void addPreRenderFilter(NotifFilter filter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -158,8 +155,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { filter.setInvalidationListener(this::onPreRenderFilterInvalidated); } - @Override - public void addPromoter(NotifPromoter promoter) { + void addPromoter(NotifPromoter promoter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -167,8 +163,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { promoter.setInvalidationListener(this::onPromoterInvalidated); } - @Override - public void setSectionsProvider(SectionsProvider provider) { + void setSectionsProvider(SectionsProvider provider) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -176,8 +171,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { provider.setInvalidationListener(this::onSectionsProviderInvalidated); } - @Override - public void setComparators(List<NotifComparator> comparators) { + void setComparators(List<NotifComparator> comparators) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -188,8 +182,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { } } - @Override - public List<ListEntry> getActiveNotifs() { + List<ListEntry> getShadeList() { Assert.isMainThread(); return mReadOnlyNotifList; } @@ -275,7 +268,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { } /** - * The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for + * The core algorithm of the pipeline. See the top comment in {@link NotifPipeline} for * details on our contracts with other code. * * Once the build starts we are very careful to protect against reentrant code. Anything that diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt index b6218b4a9c47..143de8a27816 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection +package com.android.systemui.statusbar.notification.collection.coalescer import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java index ac51178e26d7..2c6a165cc550 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection; +package com.android.systemui.statusbar.notification.collection.coalescer; import java.util.ArrayList; import java.util.List; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 069c15f9d7a2..8076616de05e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection; +package com.android.systemui.statusbar.notification.collection.coalescer; import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT; import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT; @@ -259,7 +259,7 @@ public class GroupCoalescer implements Dumpable { pw.println("Coalesced notifications:"); for (EventBatch batch : mBatches.values()) { pw.println(" Batch " + batch.mGroupKey + ":"); - pw.println(" Created" + (now - batch.mCreatedTimestamp) + "ms ago"); + pw.println(" Created " + (now - batch.mCreatedTimestamp) + "ms ago"); for (CoalescedEvent event : batch.mMembers) { pw.println(" " + event.getKey()); eventCount++; @@ -299,5 +299,5 @@ public class GroupCoalescer implements Dumpable { void onNotificationBatchPosted(List<CoalescedEvent> events); } - private static final int GROUP_LINGER_DURATION = 40; + private static final int GROUP_LINGER_DURATION = 500; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java index 898918eb076d..c1a11b2f64c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java @@ -16,27 +16,16 @@ package com.android.systemui.statusbar.notification.collection.coordinator; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; /** - * Interface for registering callbacks to the {@link NewNotifPipeline}. - * - * This includes registering: - * {@link Pluggable}s to the {@link NotifListBuilder} - * {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s to {@link NotifCollection} + * Interface for registering callbacks to the {@link NotifPipeline}. */ public interface Coordinator { - /** * Called after the NewNotifPipeline is initialized. - * Coordinators should register their {@link Pluggable}s to the notifListBuilder - * and their {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s - * to the notifCollection in this method. + * Coordinators should register their listeners and {@link Pluggable}s to the pipeline. */ - void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder); + void attach(NotifPipeline pipeline); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java index 5e7dd98fa9da..625d1b9686e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java @@ -23,9 +23,8 @@ import android.content.pm.PackageManager; import android.os.RemoteException; import android.service.notification.StatusBarNotification; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -52,10 +51,10 @@ public class DeviceProvisionedCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); - notifListBuilder.addPreGroupFilter(mNotifFilter); + pipeline.addPreGroupFilter(mNotifFilter); } private final NotifFilter mNotifFilter = new NotifFilter(TAG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java index 62342b13f9cf..da119c1502c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java @@ -25,12 +25,11 @@ import android.util.ArraySet; import com.android.systemui.ForegroundServiceController; import com.android.systemui.appops.AppOpsController; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import java.util.HashMap; import java.util.Map; @@ -57,7 +56,7 @@ public class ForegroundCoordinator implements Coordinator { private final AppOpsController mAppOpsController; private final Handler mMainHandler; - private NotifCollection mNotifCollection; + private NotifPipeline mNotifPipeline; @Inject public ForegroundCoordinator( @@ -70,20 +69,20 @@ public class ForegroundCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { - mNotifCollection = notifCollection; + public void attach(NotifPipeline pipeline) { + mNotifPipeline = pipeline; // extend the lifetime of foreground notification services to show for at least 5 seconds - mNotifCollection.addNotificationLifetimeExtender(mForegroundLifetimeExtender); + mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender); // listen for new notifications to add appOps - mNotifCollection.addCollectionListener(mNotifCollectionListener); + mNotifPipeline.addCollectionListener(mNotifCollectionListener); // when appOps change, update any relevant notifications to update appOps for mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged); // filter out foreground service notifications that aren't necessary anymore - notifListBuilder.addPreGroupFilter(mNotifFilter); + mNotifPipeline.addPreGroupFilter(mNotifFilter); } /** @@ -230,7 +229,7 @@ public class ForegroundCoordinator implements Coordinator { } private NotificationEntry findNotificationEntryWithKey(String key) { - for (NotificationEntry entry : mNotifCollection.getNotifs()) { + for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) { if (entry.getKey().equals(key)) { return entry; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index db107f531e9e..a26ee5450d60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -39,9 +39,8 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -86,9 +85,9 @@ public class KeyguardCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { setupInvalidateNotifListCallbacks(); - notifListBuilder.addPreRenderFilter(mNotifFilter); + pipeline.addPreRenderFilter(mNotifFilter); } private final NotifFilter mNotifFilter = new NotifFilter(TAG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index eeb54abf92b4..562a618126de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -18,11 +18,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.FeatureFlags; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -33,8 +32,8 @@ import javax.inject.Inject; import javax.inject.Singleton; /** - * Handles the attachment of the {@link NotifListBuilder} and {@link NotifCollection} to the - * {@link Coordinator}s, so that the Coordinators can register their respective callbacks. + * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the + * Coordinators can register their respective callbacks. */ @Singleton public class NotifCoordinators implements Dumpable { @@ -63,19 +62,18 @@ public class NotifCoordinators implements Dumpable { } /** - * Sends the initialized notifListBuilder and notifCollection to each - * coordinator to indicate the notifListBuilder is ready to accept {@link Pluggable}s - * and the notifCollection is ready to accept {@link NotifCollectionListener}s and - * {@link NotifLifetimeExtender}s. + * Sends the pipeline to each coordinator when the pipeline is ready to accept + * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s. */ - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { for (Coordinator c : mCoordinators) { - c.attach(notifCollection, notifListBuilder); + c.attach(pipeline); } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(); pw.println(TAG + ":"); for (Coordinator c : mCoordinators) { pw.println("\t" + c.getClass()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index a14f0e1cf631..20c9cbc8790d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -16,13 +16,12 @@ package com.android.systemui.statusbar.notification.collection.coordinator; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifInflater; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.logging.NotifEvent; import com.android.systemui.statusbar.notification.logging.NotifLog; @@ -55,10 +54,10 @@ public class PreparationCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { - notifCollection.addCollectionListener(mNotifCollectionListener); - notifListBuilder.addPreRenderFilter(mNotifInflationErrorFilter); - notifListBuilder.addPreRenderFilter(mNotifInflatingFilter); + public void attach(NotifPipeline pipeline) { + pipeline.addCollectionListener(mNotifCollectionListener); + pipeline.addPreRenderFilter(mNotifInflationErrorFilter); + pipeline.addPreRenderFilter(mNotifInflatingFilter); } private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 0751aa814215..7e9e76096873 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -17,9 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import javax.inject.Inject; @@ -43,10 +42,10 @@ public class RankingCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); - notifListBuilder.addPreGroupFilter(mNotifFilter); + pipeline.addPreGroupFilter(mNotifFilter); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java index fc04827a9d6a..ea0ece444a67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.inflation; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java index 7504e863ca73..3f500644b184 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.inflation; import android.annotation.Nullable; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Used by the {@link NotificationEntryManager}. When notifications are added or updated, the binder diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 6dc647d33046..1ab20a95ca6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.inflation; +import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; @@ -29,7 +30,6 @@ import android.util.Log; import android.view.ViewGroup; import com.android.internal.util.NotificationMessagingUtil; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -39,10 +39,11 @@ import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -52,57 +53,67 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + /** Handles inflating and updating views for notifications. */ +@Singleton public class NotificationRowBinderImpl implements NotificationRowBinder { private static final String TAG = "NotificationViewManager"; - private final NotificationGroupManager mGroupManager = - Dependency.get(NotificationGroupManager.class); - private final NotificationGutsManager mGutsManager = - Dependency.get(NotificationGutsManager.class); - private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = - Dependency.get(NotificationInterruptionStateProvider.class); + private final NotificationGroupManager mGroupManager; + private final NotificationGutsManager mGutsManager; + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final Context mContext; + private final NotificationRowContentBinder mRowContentBinder; private final NotificationMessagingUtil mMessagingUtil; private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; + private final NotificationRemoteInputManager mNotificationRemoteInputManager; + private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; private final boolean mAllowLongPress; private final KeyguardBypassController mKeyguardBypassController; private final StatusBarStateController mStatusBarStateController; - private NotificationRemoteInputManager mRemoteInputManager; private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; private HeadsUpManager mHeadsUpManager; - private NotificationContentInflater.InflationCallback mInflationCallback; + private NotificationRowContentBinder.InflationCallback mInflationCallback; private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; private final NotificationLogger mNotificationLogger; + @Inject public NotificationRowBinderImpl( Context context, - boolean allowLongPress, + NotificationRemoteInputManager notificationRemoteInputManager, + NotificationLockscreenUserManager notificationLockscreenUserManager, + NotificationRowContentBinder rowContentBinder, + @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, KeyguardBypassController keyguardBypassController, StatusBarStateController statusBarStateController, + NotificationGroupManager notificationGroupManager, + NotificationGutsManager notificationGutsManager, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationLogger logger) { mContext = context; + mRowContentBinder = rowContentBinder; mMessagingUtil = new NotificationMessagingUtil(context); + mNotificationRemoteInputManager = notificationRemoteInputManager; + mNotificationLockscreenUserManager = notificationLockscreenUserManager; mAllowLongPress = allowLongPress; mKeyguardBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; + mGroupManager = notificationGroupManager; + mGutsManager = notificationGutsManager; + mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mNotificationLogger = logger; } - private NotificationRemoteInputManager getRemoteInputManager() { - if (mRemoteInputManager == null) { - mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); - } - return mRemoteInputManager; - } - /** * Sets up late-bound dependencies for this component. */ @@ -117,7 +128,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mOnAppOpsClickListener = mGutsManager::openGuts; } - public void setInflationCallback(NotificationContentInflater.InflationCallback callback) { + public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { mInflationCallback = callback; } @@ -156,19 +167,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private void bindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row, Runnable onDismissRunnable) { - row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey()); - row.setBypassController(mKeyguardBypassController); - row.setStatusBarStateController(mStatusBarStateController); - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setOnExpandClickListener(mPresenter); - row.setInflationCallback(mInflationCallback); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } - mListContainer.bindRow(row); - getRemoteInputManager().bindRow(row); - // Get the app name. // Note that Notification.Builder#bindHeaderAppName has similar logic // but since this field is used in the guts, it must be accurate. @@ -186,15 +184,33 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } catch (PackageManager.NameNotFoundException e) { // Do nothing } - row.setAppName(appname); + + row.initialize( + appname, + sbn.getKey(), + mExpansionLogger, + mKeyguardBypassController, + mGroupManager, + mHeadsUpManager, + mRowContentBinder, + mPresenter); + + // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely + row.setStatusBarStateController(mStatusBarStateController); + row.setInflationCallback(mInflationCallback); + row.setAppOpsOnClickListener(mOnAppOpsClickListener); + if (mAllowLongPress) { + row.setLongPressListener(mGutsManager::openGuts); + } + mListContainer.bindRow(row); + mNotificationRemoteInputManager.bindRow(row); + row.setOnDismissRunnable(onDismissRunnable); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (ENABLE_REMOTE_INPUT) { row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } @@ -263,8 +279,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP); } - row.setNeedsRedaction( - Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry)); + row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); row.inflateViews(); // bind the click event to the content area diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java index 986ee17cc906..15f312dbdc00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java @@ -19,8 +19,8 @@ package com.android.systemui.statusbar.notification.collection.init; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -36,7 +36,7 @@ public class FakePipelineConsumer implements Dumpable { private List<ListEntry> mEntries = Collections.emptyList(); /** Attach the consumer to the pipeline. */ - public void attach(NotifListBuilderImpl listBuilder) { + public void attach(ShadeListBuilder listBuilder) { listBuilder.setOnRenderListListener(this::onBuildComplete); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java index 8d3d0ff43deb..959b00211c63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java @@ -24,10 +24,11 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -39,10 +40,11 @@ import javax.inject.Singleton; * Initialization code for the new notification pipeline. */ @Singleton -public class NewNotifPipeline implements Dumpable { +public class NotifPipelineInitializer implements Dumpable { + private final NotifPipeline mPipelineWrapper; private final GroupCoalescer mGroupCoalescer; private final NotifCollection mNotifCollection; - private final NotifListBuilderImpl mNotifPipeline; + private final ShadeListBuilder mListBuilder; private final NotifCoordinators mNotifPluggableCoordinators; private final NotifInflaterImpl mNotifInflater; private final DumpController mDumpController; @@ -51,17 +53,19 @@ public class NewNotifPipeline implements Dumpable { private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer(); @Inject - public NewNotifPipeline( + public NotifPipelineInitializer( + NotifPipeline pipelineWrapper, GroupCoalescer groupCoalescer, NotifCollection notifCollection, - NotifListBuilderImpl notifPipeline, + ShadeListBuilder listBuilder, NotifCoordinators notifCoordinators, NotifInflaterImpl notifInflater, DumpController dumpController, FeatureFlags featureFlags) { + mPipelineWrapper = pipelineWrapper; mGroupCoalescer = groupCoalescer; mNotifCollection = notifCollection; - mNotifPipeline = notifPipeline; + mListBuilder = listBuilder; mNotifPluggableCoordinators = notifCoordinators; mDumpController = dumpController; mNotifInflater = notifInflater; @@ -81,11 +85,11 @@ public class NewNotifPipeline implements Dumpable { } // Wire up coordinators - mFakePipelineConsumer.attach(mNotifPipeline); - mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline); + mNotifPluggableCoordinators.attach(mPipelineWrapper); // Wire up pipeline - mNotifPipeline.attach(mNotifCollection); + mFakePipelineConsumer.attach(mListBuilder); + mListBuilder.attach(mNotifCollection); mNotifCollection.attach(mGroupCoalescer); mGroupCoalescer.attach(notificationService); @@ -99,5 +103,5 @@ public class NewNotifPipeline implements Dumpable { mGroupCoalescer.dump(fd, pw, args); } - private static final String TAG = "NewNotifPipeline"; + private static final String TAG = "NotifPipeline"; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java deleted file mode 100644 index 758092417ae0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.listbuilder; - -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider; - -import java.util.List; - -/** - * The system that constructs the current "notification list", the list of notifications that are - * currently being displayed to the user. - * - * The pipeline proceeds through a series of stages in order to produce the final list (see below). - * Each stage exposes hooks and listeners for other code to participate. - * - * This list differs from the canonical one we receive from system server in a few ways: - * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose - * views haven't been inflated yet. We also filter out some notifications if we're on the lock - * screen. To participate, see {@link #addFilter(NotifFilter)}. - * - Grouped: Notifications that are part of the same group are clustered together into a single - * GroupEntry. These groups are then transformed in order to remove children or completely split - * them apart. To participate, see {@link #addPromoter(NotifPromoter)}. - * - Sorted: All top-level notifications are sorted. To participate, see - * {@link #setSectionsProvider(SectionsProvider)} and {@link #setComparators(List)} - * - * The exact order of all hooks is as follows: - * 0. Collection listeners are fired (see {@link NotifCollection}). - * 1. NotifFilters are called on each notification currently in NotifCollection. - * 2. Initial grouping is performed (NotificationEntries will have their parents set - * appropriately). - * 3. OnBeforeTransformGroupListeners are fired - * 4. NotifPromoters are called on each notification with a parent - * 5. OnBeforeSortListeners are fired - * 6. SectionsProvider is called on each top-level entry in the list - * 7. The top-level entries are sorted using the provided NotifComparators (plus some additional - * built-in logic). - * 8. OnBeforeRenderListListeners are fired - * 9. The list is handed off to the view layer to be rendered. - */ -public interface NotifListBuilder { - - /** - * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters - * are called on each notification in the order that they were registered. If any filter - * returns true, the notification is removed from the pipeline (and no other filters are - * called on that notif). - */ - void addPreGroupFilter(NotifFilter filter); - - /** - * Registers a promoter with the pipeline. Promoters are able to promote child notifications to - * top-level, i.e. move a notification that would be a child of a group and make it appear - * ungrouped. Promoters are called on each child notification in the order that they are - * registered. If any promoter returns true, the notification is removed from the group (and no - * other promoters are called on it). - */ - void addPromoter(NotifPromoter promoter); - - /** - * Assigns sections to each top-level entry, where a section is simply an integer. Sections are - * the primary metric by which top-level entries are sorted; NotifComparators are only consulted - * when two entries are in the same section. The pipeline doesn't assign any particular meaning - * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple - * numerical comparison. - */ - void setSectionsProvider(SectionsProvider provider); - - /** - * Comparators that are used to sort top-level entries that share the same section. The - * comparators are executed in order until one of them returns a non-zero result. If all return - * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when). - */ - void setComparators(List<NotifComparator> comparators); - - /** - * Registers a filter with the pipeline to filter right before rendering the list (after - * pre-group filtering, grouping, promoting and sorting occurs). Filters are - * called on each notification in the order that they were registered. If any filter returns - * true, the notification is removed from the pipeline (and no other filters are called on that - * notif). - */ - void addPreRenderFilter(NotifFilter filter); - - /** - * Called after notifications have been filtered and after the initial grouping has been - * performed but before NotifPromoters have had a chance to promote children out of groups. - */ - void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener); - - /** - * Called after notifs have been filtered and groups have been determined but before sections - * have been determined or the notifs have been sorted. - */ - void addOnBeforeSortListener(OnBeforeSortListener listener); - - /** - * Called at the end of the pipeline after the notif list has been finalized but before it has - * been handed off to the view layer. - */ - void addOnBeforeRenderListListener(OnBeforeRenderListListener listener); - - /** - * Returns a read-only view in to the current notification list. If this method is called - * during pipeline execution it will return the current state of the list, which will likely - * be only partially-generated. - */ - List<ListEntry> getActiveNotifs(); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java index f6ca12d83fdd..44a27a4b546a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java @@ -17,10 +17,11 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; -/** See {@link NotifListBuilder#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */ +/** See {@link NotifPipeline#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */ public interface OnBeforeRenderListListener { /** * Called at the end of the pipeline after the notif list has been finalized but before it has diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java index 7be7ac03e1f1..56cfe5cb3716 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java @@ -17,10 +17,11 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; -/** See {@link NotifListBuilder#addOnBeforeSortListener(OnBeforeSortListener)} */ +/** See {@link NotifPipeline#addOnBeforeSortListener(OnBeforeSortListener)} */ public interface OnBeforeSortListener { /** * Called after the notif list has been filtered and grouped but before sections have been diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java index d7a081510655..0dc4df0da066 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java @@ -17,13 +17,14 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import java.util.List; /** * See - * {@link NotifListBuilder#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)} + * {@link NotifPipeline#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)} */ public interface OnBeforeTransformGroupsListener { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java index 084d03810ad9..1897ba2319ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java @@ -18,13 +18,13 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import android.annotation.IntDef; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Used by {@link NotifListBuilderImpl} to track its internal state machine. + * Used by {@link ShadeListBuilder} to track its internal state machine. */ public class PipelineState { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java index a191c830537d..0d150edee128 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java @@ -17,13 +17,13 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.Comparator; import java.util.List; /** - * Pluggable for participating in notif sorting. See {@link NotifListBuilder#setComparators(List)}. + * Pluggable for participating in notif sorting. See {@link NotifPipeline#setComparators(List)}. */ public abstract class NotifComparator extends Pluggable<NotifComparator> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java index e6189edc2f3b..8f575cdd8918 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; /** * Pluggable for participating in notif filtering. - * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}. + * See {@link NotifPipeline#addPreGroupFilter} and {@link NotifPipeline#addPreRenderFilter}. */ public abstract class NotifFilter extends Pluggable<NotifFilter> { protected NotifFilter(String name) { @@ -35,9 +35,9 @@ public abstract class NotifFilter extends Pluggable<NotifFilter> { * however. If another filter returns true before yours, we'll skip straight to the next notif. * * @param entry The entry in question. - * If this filter is registered via {@link NotifListBuilder#addPreGroupFilter}, + * If this filter is registered via {@link NotifPipeline#addPreGroupFilter}, * this entry will not have any grouping nor sorting information. - * If this filter is registered via {@link NotifListBuilder#addPreRenderFilter}, + * If this filter is registered via {@link NotifPipeline#addPreRenderFilter}, * this entry will have grouping and sorting information. * @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of * pipeline execution. This value will be the same for all pluggable calls made diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java index 84e16f432740..5fce4462aede 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java @@ -16,13 +16,13 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; /** * Pluggable for participating in notif promotion. Notif promoters can upgrade notifications * from being children of a group to top-level notifications. See - * {@link NotifListBuilder#addPromoter(NotifPromoter)}. + * {@link NotifPipeline#addPromoter}. */ public abstract class NotifPromoter extends Pluggable<NotifPromoter> { protected NotifPromoter(String name) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java index f9ce197c6547..4270408d0c66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java @@ -18,10 +18,10 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.plugg import android.annotation.Nullable; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; /** - * Generic superclass for chunks of code that can plug into the {@link NotifListBuilder}. + * Generic superclass for chunks of code that can plug into the {@link NotifPipeline}. * * A pluggable is fundamentally three things: * 1. A name (for debugging purposes) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java index 87aaea007e65..4023474bf6a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.Collection; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java index ecce6ea1b211..b2686864cc43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; import android.service.notification.NotificationStats.DismissalSentiment; import android.service.notification.NotificationStats.DismissalSurface; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 032620e14336..9cbc7d7efa66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; +import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Listener interface for {@link NotifCollection}. @@ -36,7 +38,9 @@ public interface NotifCollectionListener { } /** - * Called immediately after a notification has been removed from the collection. + * Called whenever a notification is retracted by system server. This method is not called + * immediately after a user dismisses a notification: we wait until we receive confirmation from + * system server before considering the notification removed. */ default void onEntryRemoved( NotificationEntry entry, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java index 2c7b13866c10..05f5ea85bd4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; +import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * A way for other code to temporarily extend the lifetime of a notification after it has been diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java index c6c36ee17873..02acc8149cc4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java @@ -22,8 +22,8 @@ import android.service.notification.StatusBarNotification; import com.android.systemui.log.RichEvent; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -67,7 +67,7 @@ public class NotifEvent extends RichEvent { } /** - * @return if this event occurred in {@link NotifListBuilder} + * @return if this event occurred in {@link ShadeListBuilder} */ static boolean isListBuilderEvent(@EventType int type) { return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES); @@ -161,7 +161,7 @@ public class NotifEvent extends RichEvent { private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length; /** - * Events related to {@link NotifListBuilder} + * Events related to {@link ShadeListBuilder} */ public static final int WARN = 0; public static final int ON_BUILD_LIST = 1; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index 78eaf3ee10a4..452d1eb95aab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.people import android.app.Notification +import android.content.Context import android.service.notification.StatusBarNotification +import android.util.FeatureFlagUtils import javax.inject.Inject import javax.inject.Singleton @@ -27,10 +29,16 @@ interface PeopleNotificationIdentifier { @Singleton class PeopleNotificationIdentifierImpl @Inject constructor( - private val personExtractor: NotificationPersonExtractor + private val personExtractor: NotificationPersonExtractor, + private val context: Context ) : PeopleNotificationIdentifier { override fun isPeopleNotification(sbn: StatusBarNotification) = - sbn.notification.notificationStyle == Notification.MessagingStyle::class.java || + (sbn.notification.notificationStyle == Notification.MessagingStyle::class.java && + (sbn.notification.shortcutId != null || + FeatureFlagUtils.isEnabled( + context, + FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ + ))) || personExtractor.isPersonNotification(sbn) }
\ No newline at end of file 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 3c247df692f4..a8a35d07b3f0 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 @@ -65,7 +65,6 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.Chronometer; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -150,7 +149,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private StatusBarStateController mStatusbarStateController; private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; - private final NotificationContentInflater mNotificationInflater; + private NotificationRowContentBinder mNotificationContentBinder; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mMaxHeadsUpHeightBeforeN; @@ -464,7 +463,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Inflate views based off the inflation flags set. Inflation happens asynchronously. */ public void inflateViews() { - mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, false /* forceInflate */, mInflationCallback); } @@ -478,7 +477,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // View should not be reinflated in the future clearInflationFlags(inflationFlag); Runnable freeViewRunnable = - () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag); + () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag); switch (inflationFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, @@ -742,23 +741,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsHeadsUp || mHeadsupDisappearRunning; } - - public void setGroupManager(NotificationGroupManager groupManager) { - mGroupManager = groupManager; - mPrivateLayout.setGroupManager(groupManager); - } - public void setRemoteInputController(RemoteInputController r) { mPrivateLayout.setRemoteInputController(r); } - public void setAppName(String appName) { - mAppName = appName; - if (mMenuRow != null && mMenuRow.getMenuView() != null) { - mMenuRow.setAppName(mAppName); - } - } - public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } @@ -852,7 +838,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mIsChildInGroup = isChildInGroup; if (mIsLowPriority) { int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - mNotificationInflater.bindContent(mEntry, this, flags, mBindParams, + mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams, false /* forceInflate */, mInflationCallback); } } @@ -1105,10 +1091,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPrivateLayout.getContractedNotificationHeader(); } - public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { - mOnExpandClickListener = onExpandClickListener; - } - public void setLongPressListener(LongPressListener longPressListener) { mLongPressListener = longPressListener; } @@ -1131,10 +1113,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { - mHeadsUpManager = headsUpManager; - } - public HeadsUpManager getHeadsUpManager() { return mHeadsUpManager; } @@ -1259,7 +1237,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reInflateViews(); } mEntry.getSbn().clearPackageContext(); - mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, true /* forceInflate */, mInflationCallback); } @@ -1634,10 +1612,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mBindParams.usesIncreasedHeadsUpHeight = use; } - public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { - mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler); - } - /** * Set callback for notification content inflation * @@ -1652,7 +1626,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNeedsRedaction = needsRedaction; if (needsRedaction) { setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, + mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, mBindParams, false /* forceInflate */, mInflationCallback); } else { clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); @@ -1661,18 +1635,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - @VisibleForTesting - public NotificationContentInflater getNotificationInflater() { - return mNotificationInflater; - } - public interface ExpansionLogger { void logNotificationExpansion(String key, boolean userAction, boolean expanded); } public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); - mNotificationInflater = new NotificationContentInflater(); mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); @@ -1680,8 +1648,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView initDimens(); } - public void setBypassController(KeyguardBypassController bypassController) { + /** + * Initialize row. + */ + public void initialize( + String appName, + String notificationKey, + ExpansionLogger logger, + KeyguardBypassController bypassController, + NotificationGroupManager groupManager, + HeadsUpManager headsUpManager, + NotificationRowContentBinder rowContentBinder, + OnExpandClickListener onExpandClickListener) { + mAppName = appName; + if (mMenuRow != null && mMenuRow.getMenuView() != null) { + mMenuRow.setAppName(mAppName); + } + mLogger = logger; + mLoggingKey = notificationKey; mBypassController = bypassController; + mGroupManager = groupManager; + mPrivateLayout.setGroupManager(groupManager); + mHeadsUpManager = headsUpManager; + mNotificationContentBinder = rowContentBinder; + mOnExpandClickListener = onExpandClickListener; } public void setStatusBarStateController(StatusBarStateController statusBarStateController) { @@ -2920,11 +2910,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return 0; } - public void setExpansionLogger(ExpansionLogger logger, String key) { - mLogger = logger; - mLoggingKey = key; - } - public void onExpandedByGesture(boolean userExpanded) { int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java new file mode 100644 index 000000000000..c11c60fcdd04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import android.widget.RemoteViews; + +import androidx.annotation.Nullable; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +/** + * Caches {@link RemoteViews} for a notification's content views. + */ +public interface NotifRemoteViewCache { + + /** + * Whether the notification has the remote view cached + * + * @param entry notification + * @param flag inflation flag for content view + * @return true if the remote view is cached + */ + boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag); + + /** + * Get the remote view for the content flag specified. + * + * @param entry notification + * @param flag inflation flag for the content view + * @return the remote view if it is cached, null otherwise + */ + @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag); + + /** + * Cache a remote view for a given content flag on a notification. + * + * @param entry notification + * @param flag inflation flag for the content view + * @param remoteView remote view to store + */ + void putCachedView( + NotificationEntry entry, + @InflationFlag int flag, + RemoteViews remoteView); + + /** + * Remove a cached remote view for a given content flag on a notification. + * + * @param entry notification + * @param flag inflation flag for the content view + */ + void removeCachedView(NotificationEntry entry, @InflationFlag int flag); + + /** + * Clear a notification's remote view cache. + * + * @param entry notification + */ + void clearCache(NotificationEntry entry); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java new file mode 100644 index 000000000000..a6e5c2b79968 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import android.util.ArrayMap; +import android.util.SparseArray; +import android.widget.RemoteViews; + +import androidx.annotation.Nullable; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import java.util.Map; + +import javax.inject.Inject; + +/** + * Implementation of remote view cache that keeps remote views cached for all active notifications. + */ +public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { + private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews = + new ArrayMap<>(); + + @Inject + NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) { + entryManager.addNotificationEntryListener(mEntryListener); + } + + @Override + public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) { + return getCachedView(entry, flag) != null; + } + + @Override + public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) { + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return null; + } + return contentViews.get(flag); + } + + @Override + public void putCachedView( + NotificationEntry entry, + @InflationFlag int flag, + RemoteViews remoteView) { + /** + * TODO: We should be more strict here in the future (i.e. throw an exception) if the + * content views aren't created. We don't do that right now because we have edge cases + * where we may bind/unbind content after a notification is removed. + */ + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return; + } + contentViews.put(flag, remoteView); + } + + @Override + public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) { + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return; + } + contentViews.remove(flag); + } + + @Override + public void clearCache(NotificationEntry entry) { + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return; + } + contentViews.clear(); + } + + private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationEntry entry) { + mNotifCachedContentViews.put(entry, new SparseArray<>()); + } + + @Override + public void onEntryRemoved( + NotificationEntry entry, + @Nullable NotificationVisibility visibility, + boolean removedByUser) { + mNotifCachedContentViews.remove(entry); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 30f22ac5e161..e1a6747b5398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; @@ -26,7 +27,6 @@ import android.content.Context; import android.os.AsyncTask; import android.os.CancellationSignal; import android.service.notification.StatusBarNotification; -import android.util.ArrayMap; import android.util.Log; import android.view.View; import android.widget.RemoteViews; @@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.Dependency; import com.android.systemui.statusbar.InflationTask; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; @@ -49,17 +50,30 @@ import com.android.systemui.util.Assert; import java.util.HashMap; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by * asynchronously building the content's {@link RemoteViews} and applying it to the row. */ +@Singleton +@VisibleForTesting(visibility = PACKAGE) public class NotificationContentInflater implements NotificationRowContentBinder { public static final String TAG = "NotifContentInflater"; - private RemoteViews.OnClickHandler mRemoteViewClickHandler; private boolean mInflateSynchronously = false; - private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>(); + private final NotificationRemoteInputManager mRemoteInputManager; + private final NotifRemoteViewCache mRemoteViewCache; + + @Inject + public NotificationContentInflater( + NotifRemoteViewCache remoteViewCache, + NotificationRemoteInputManager remoteInputManager) { + mRemoteViewCache = remoteViewCache; + mRemoteInputManager = remoteInputManager; + } @Override public void bindContent( @@ -76,27 +90,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder return; } - StatusBarNotification sbn = row.getEntry().getSbn(); + StatusBarNotification sbn = entry.getSbn(); // To check if the notification has inline image and preload inline image if necessary. row.getImageResolver().preloadImages(sbn.getNotification()); if (forceInflate) { - mCachedContentViews.clear(); + mRemoteViewCache.clearCache(entry); } AsyncInflationTask task = new AsyncInflationTask( - sbn, mInflateSynchronously, contentToBind, - mCachedContentViews, + mRemoteViewCache, + entry, row, bindParams.isLowPriority, bindParams.isChildInGroup, bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, callback, - mRemoteViewClickHandler); + mRemoteInputManager.getRemoteViewsOnClickHandler()); if (mInflateSynchronously) { task.onPostExecute(task.doInBackground()); } else { @@ -123,13 +137,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, row.getHeadsUpManager(), row.getExistingSmartRepliesAndActions()); + apply( inflateSynchronously, result, reInflateFlags, - mCachedContentViews, + mRemoteViewCache, + entry, row, - mRemoteViewClickHandler, + mRemoteInputManager.getRemoteViewsOnClickHandler(), null); return result; } @@ -149,7 +165,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder int curFlag = 1; while (contentToUnbind != 0) { if ((contentToUnbind & curFlag) != 0) { - freeNotificationView(row, curFlag); + freeNotificationView(entry, row, curFlag); } contentToUnbind &= ~curFlag; curFlag = curFlag << 1; @@ -157,34 +173,25 @@ public class NotificationContentInflater implements NotificationRowContentBinder } /** - * Set click handler for notification remote views - * - * @param remoteViewClickHandler click handler for remote views - */ - public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { - mRemoteViewClickHandler = remoteViewClickHandler; - } - - /** * Frees the content view associated with the inflation flag. Will only succeed if the * view is safe to remove. * * @param inflateFlag the flag corresponding to the content view which should be freed */ - private void freeNotificationView(ExpandableNotificationRow row, + private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row, @InflationFlag int inflateFlag) { switch (inflateFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) { row.getPrivateLayout().setHeadsUpChild(null); - mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP); + mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null); } break; case FLAG_CONTENT_VIEW_PUBLIC: if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { row.getPublicLayout().setContractedChild(null); - mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC); + mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC); } break; case FLAG_CONTENT_VIEW_CONTRACTED: @@ -245,11 +252,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result; } - public static CancellationSignal apply( + private static CancellationSignal apply( boolean inflateSynchronously, InflationProgress result, @InflationFlag int reInflateFlags, - ArrayMap<Integer, RemoteViews> cachedContentViews, + NotifRemoteViewCache remoteViewCache, + NotificationEntry entry, ExpandableNotificationRow row, RemoteViews.OnClickHandler remoteViewClickHandler, @Nullable InflationCallback callback) { @@ -261,7 +269,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newContentView, - cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -273,8 +281,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newContentView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, - row, isNewView, remoteViewClickHandler, callback, privateLayout, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); @@ -285,7 +293,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newExpandedView != null) { boolean isNewView = !canReapplyRemoteView(result.newExpandedView, - cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -297,8 +305,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newExpandedView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, - cachedContentViews, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getExpandedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, @@ -311,7 +319,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newHeadsUpView != null) { boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView, - cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -323,8 +331,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newHeadsUpView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, - cachedContentViews, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getHeadsUpChild(), privateLayout.getVisibleWrapper( VISIBLE_TYPE_HEADSUP), runningInflations, @@ -336,7 +344,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newPublicView, - cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -348,15 +356,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newPublicView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, - row, isNewView, remoteViewClickHandler, callback, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, publicLayout, publicLayout.getContractedChild(), publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); } // Let's try to finish, maybe nobody is even inflating anything - finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row); + finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry, + row); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( () -> runningInflations.values().forEach(CancellationSignal::cancel)); @@ -369,7 +378,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder final InflationProgress result, final @InflationFlag int reInflateFlags, @InflationFlag int inflationId, - final ArrayMap<Integer, RemoteViews> cachedContentViews, + final NotifRemoteViewCache remoteViewCache, + final NotificationEntry entry, final ExpandableNotificationRow row, boolean isNewView, RemoteViews.OnClickHandler remoteViewClickHandler, @@ -422,8 +432,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder existingWrapper.onReinflated(); } runningInflations.remove(inflationId); - finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, - callback, row); + finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, + callback, entry, row); } @Override @@ -488,11 +498,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder * @return true if the inflation was finished */ private static boolean finishIfDone(InflationProgress result, - @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews, + @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, HashMap<Integer, CancellationSignal> runningInflations, - @Nullable InflationCallback endListener, ExpandableNotificationRow row) { + @Nullable InflationCallback endListener, NotificationEntry entry, + ExpandableNotificationRow row) { Assert.isMainThread(); - NotificationEntry entry = row.getEntry(); NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); if (runningInflations.isEmpty()) { @@ -500,23 +510,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.inflatedContentView != null) { // New view case privateLayout.setContractedChild(result.inflatedContentView); - cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, + result.newContentView); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { // Reinflation case. Only update if it's still cached (i.e. view has not been // freed while inflating). - cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, + result.newContentView); } } if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { if (result.inflatedExpandedView != null) { privateLayout.setExpandedChild(result.inflatedExpandedView); - cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, + result.newExpandedView); } else if (result.newExpandedView == null) { privateLayout.setExpandedChild(null); - cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) { - cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, + result.newExpandedView); } if (result.newExpandedView != null) { privateLayout.setExpandedInflatedSmartReplies( @@ -530,12 +544,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { if (result.inflatedHeadsUpView != null) { privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); - cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, + result.newHeadsUpView); } else if (result.newHeadsUpView == null) { privateLayout.setHeadsUpChild(null); - cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) { - cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, + result.newHeadsUpView); } if (result.newHeadsUpView != null) { privateLayout.setHeadsUpInflatedSmartReplies( @@ -548,16 +564,18 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { if (result.inflatedPublicView != null) { publicLayout.setContractedChild(result.inflatedPublicView); - cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) { - cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, + result.newPublicView); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, + result.newPublicView); } } entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags); + endListener.onAsyncInflationFinished(entry, reInflateFlags); } return true; } @@ -615,7 +633,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> implements InflationCallback, InflationTask { - private final StatusBarNotification mSbn; + private final NotificationEntry mEntry; private final Context mContext; private final boolean mInflateSynchronously; private final boolean mIsLowPriority; @@ -624,17 +642,17 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; private @InflationFlag int mReInflateFlags; - private final ArrayMap<Integer, RemoteViews> mCachedContentViews; + private final NotifRemoteViewCache mRemoteViewCache; private ExpandableNotificationRow mRow; private Exception mError; private RemoteViews.OnClickHandler mRemoteViewClickHandler; private CancellationSignal mCancellationSignal; private AsyncInflationTask( - StatusBarNotification notification, boolean inflateSynchronously, @InflationFlag int reInflateFlags, - ArrayMap<Integer, RemoteViews> cachedContentViews, + NotifRemoteViewCache cache, + NotificationEntry entry, ExpandableNotificationRow row, boolean isLowPriority, boolean isChildInGroup, @@ -642,11 +660,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder boolean usesIncreasedHeadsUpHeight, InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) { + mEntry = entry; mRow = row; - mSbn = notification; mInflateSynchronously = inflateSynchronously; mReInflateFlags = reInflateFlags; - mCachedContentViews = cachedContentViews; + mRemoteViewCache = cache; mContext = mRow.getContext(); mIsLowPriority = isLowPriority; mIsChildInGroup = isChildInGroup; @@ -654,7 +672,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; - NotificationEntry entry = row.getEntry(); entry.setInflationTask(this); } @@ -667,12 +684,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override protected InflationProgress doInBackground(Void... params) { try { + final StatusBarNotification sbn = mEntry.getSbn(); final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(mContext, - mSbn.getNotification()); + sbn.getNotification()); - Context packageContext = mSbn.getPackageContext(mContext); - Notification notification = mSbn.getNotification(); + Context packageContext = sbn.getPackageContext(mContext); + Notification notification = sbn.getNotification(); if (notification.isMediaNotification()) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, packageContext); @@ -681,7 +699,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); - return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(), + return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry, mRow.getContext(), packageContext, mRow.getHeadsUpManager(), mRow.getExistingSmartRepliesAndActions()); } catch (Exception e) { @@ -694,15 +712,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder protected void onPostExecute(InflationProgress result) { if (mError == null) { mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags, - mCachedContentViews, mRow, mRemoteViewClickHandler, this); + mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this); } else { handleError(mError); } } private void handleError(Exception e) { - mRow.getEntry().onInflationTaskFinished(); - StatusBarNotification sbn = mRow.getEntry().getSbn(); + mEntry.onInflationTaskFinished(); + StatusBarNotification sbn = mEntry.getSbn(); final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); @@ -736,10 +754,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { - mRow.getEntry().onInflationTaskFinished(); + mEntry.onInflationTaskFinished(); mRow.onNotificationUpdated(); if (mCallback != null) { - mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags); + mCallback.onAsyncInflationFinished(mEntry, inflatedFlags); } // Notify the resolver that the inflation task has finished, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java new file mode 100644 index 000000000000..df8653cf2406 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import javax.inject.Singleton; + +import dagger.Binds; +import dagger.Module; + +/** + * Dagger Module containing notification row and view inflation implementations. + */ +@Module +public abstract class NotificationRowModule { + /** + * Provides notification row content binder instance. + */ + @Binds + @Singleton + public abstract NotificationRowContentBinder provideNotificationRowContentBinder( + NotificationContentInflater contentBinderImpl); + + /** + * Provides notification remote view cache instance. + */ + @Binds + @Singleton + public abstract NotifRemoteViewCache provideNotifRemoteViewCache( + NotifRemoteViewCacheImpl cacheImpl); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index fe0739f9088c..896b6e570da2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -32,7 +32,6 @@ import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; @@ -428,7 +427,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * The notification is still pending inflation but we've decided that we no longer need * the content view (e.g. suppression might have changed and we decided we need to transfer * back). However, there is no way to abort just this inflation if other inflation requests - * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead + * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead * we just flag it as aborted and free when it's inflated. */ boolean mAbortOnInflation; 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 ccc86b1f8c5f..ba70cf4a39f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -29,7 +29,6 @@ import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; -import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; @@ -200,8 +199,8 @@ import com.android.systemui.statusbar.notification.NotificationListController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -365,8 +364,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final HeadsUpManagerPhone mHeadsUpManager; private final DynamicPrivacyController mDynamicPrivacyController; private final BypassHeadsUpNotifier mBypassHeadsUpNotifier; - private final boolean mAllowNotificationLongPress; - private final Lazy<NewNotifPipeline> mNewNotifPipeline; + private final Lazy<NotifPipelineInitializer> mNewNotifPipeline; private final FalsingManager mFalsingManager; private final BroadcastDispatcher mBroadcastDispatcher; private final ConfigurationController mConfigurationController; @@ -388,6 +386,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final KeyguardDismissUtil mKeyguardDismissUtil; private final ExtensionController mExtensionController; private final UserInfoControllerImpl mUserInfoControllerImpl; + private final NotificationRowBinderImpl mNotificationRowBinder; private final DismissCallbackRegistry mDismissCallbackRegistry; // expanded notifications @@ -626,8 +625,7 @@ public class StatusBar extends SystemUI implements DemoMode, HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -693,6 +691,7 @@ public class StatusBar extends SystemUI implements DemoMode, KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, + NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { super(context); mFeatureFlags = featureFlags; @@ -707,7 +706,6 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager = headsUpManagerPhone; mDynamicPrivacyController = dynamicPrivacyController; mBypassHeadsUpNotifier = bypassHeadsUpNotifier; - mAllowNotificationLongPress = allowNotificationLongPress; mNewNotifPipeline = newNotifPipeline; mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; @@ -772,6 +770,7 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardDismissUtil = keyguardDismissUtil; mExtensionController = extensionController; mUserInfoControllerImpl = userInfoControllerImpl; + mNotificationRowBinder = notificationRowBinder; mDismissCallbackRegistry = dismissCallbackRegistry; mBubbleExpandListener = @@ -1237,19 +1236,11 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarWindowViewController, this, mNotificationPanelViewController, (NotificationListContainer) mStackScroller); - final NotificationRowBinderImpl rowBinder = - new NotificationRowBinderImpl( - mContext, - mAllowNotificationLongPress, - mKeyguardBypassController, - mStatusBarStateController, - mNotificationLogger); - // TODO: inject this. mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController, mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController, mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController, - mNotificationAlertingManager, rowBinder, mKeyguardStateController, + mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController, mKeyguardIndicationController, this /* statusBar */, mShadeController, mCommandQueue, mInitController); @@ -1273,20 +1264,19 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - mEntryManager.setRowBinder(rowBinder); - rowBinder.setInflationCallback(mEntryManager); + mNotificationRowBinder.setInflationCallback(mEntryManager); } mRemoteInputUriController.attach(mEntryManager); - rowBinder.setNotificationClicker(new NotificationClicker( + mNotificationRowBinder.setNotificationClicker(new NotificationClicker( Optional.of(this), mBubbleController, mNotificationActivityStarter)); mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); mNotificationListController.bind(); if (mFeatureFlags.isNewNotifPipelineEnabled()) { - mNewNotifPipeline.get().initialize(mNotificationListener, rowBinder); + mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder); } mEntryManager.attach(mNotificationListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index 153ca22933a9..b4d5dadda5b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import android.content.Context; @@ -66,7 +65,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -117,8 +117,7 @@ public class StatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -184,6 +183,7 @@ public class StatusBarModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, + NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { return new StatusBar( context, @@ -199,7 +199,6 @@ public class StatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - allowNotificationLongPress, newNotifPipeline, falsingManager, broadcastDispatcher, @@ -265,6 +264,7 @@ public class StatusBarModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, + notificationRowBinder, dismissCallbackRegistry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 12a65169e1df..720f22964915 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -66,7 +66,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 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 6b842d5fa0b3..5916180d75ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -654,7 +654,7 @@ public class MobileSignalController extends SignalController< } boolean isDataDisabled() { - return !mPhone.isDataCapable(); + return !mPhone.isDataConnectionEnabled(); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java index 7cdba8607d86..cc6d607a60cf 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java @@ -83,17 +83,19 @@ public abstract class ConcurrencyModule { * Provide a Background-Thread Executor by default. */ @Provides + @Singleton public static Executor provideExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Background-Thread Executor. */ @Provides + @Singleton @Background public static Executor provideBackgroundExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** @@ -109,26 +111,29 @@ public abstract class ConcurrencyModule { * Provide a Background-Thread Executor by default. */ @Provides + @Singleton public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Background-Thread Executor. */ @Provides + @Singleton @Background public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Main-Thread Executor. */ @Provides + @Singleton @Main public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java index 7e7732135e3a..2bbf9507122a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java @@ -17,37 +17,69 @@ package com.android.systemui.util.concurrency; import android.os.Handler; -import android.os.HandlerExecutor; +import android.os.Looper; import android.os.Message; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; /** * Implementations of {@link DelayableExecutor} for SystemUI. */ -public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor { +public class ExecutorImpl implements DelayableExecutor { private final Handler mHandler; - public ExecutorImpl(Handler handler) { - super(handler); - mHandler = handler; + ExecutorImpl(Looper looper) { + mHandler = new Handler(looper, this::onHandleMessage); + } + + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } } @Override public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) { - Object token = new Object(); - Message m = mHandler.obtainMessage(0, token); + ExecutionToken token = new ExecutionToken(r); + Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token); mHandler.sendMessageDelayed(m, unit.toMillis(delay)); - return () -> mHandler.removeCallbacksAndMessages(token); + return token; } @Override public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) { - Object token = new Object(); - Message m = mHandler.obtainMessage(0, token); + ExecutionToken token = new ExecutionToken(r); + Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token); mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis)); - return () -> mHandler.removeCallbacksAndMessages(token); + return token; + } + + private boolean onHandleMessage(Message msg) { + if (msg.what == MSG_EXECUTE_RUNNABLE) { + ExecutionToken token = (ExecutionToken) msg.obj; + token.runnable.run(); + } else { + throw new IllegalStateException("Unrecognized message: " + msg.what); + } + return true; } + + private class ExecutionToken implements Runnable { + public final Runnable runnable; + + private ExecutionToken(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public void run() { + mHandler.removeCallbacksAndMessages(this); + } + } + + private static final int MSG_EXECUTE_RUNNABLE = 0; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index 9c9a627fa6e0..8d11b54dacb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -48,7 +48,7 @@ import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.appops.AppOpsController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -71,7 +71,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { @Mock private NotificationEntryManager mEntryManager; @Mock private AppOpsController mAppOpsController; @Mock private Handler mMainHandler; - @Mock private NotifCollection mNotifCollection; + @Mock private NotifPipeline mNotifPipeline; @Before public void setUp() throws Exception { @@ -81,7 +81,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler); mListener = new ForegroundServiceNotificationListener( - mContext, mFsc, mEntryManager, mNotifCollection); + mContext, mFsc, mEntryManager, mNotifPipeline); ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = ArgumentCaptor.forClass(NotificationEntryListener.class); verify(mEntryManager).addNotificationEntryListener( diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java index 0c53b03de4d2..2ecc8ea8400c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java +++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java @@ -52,6 +52,7 @@ public class TestableDependency extends Dependency { @Override protected <T> T createDependency(Object key) { if (mObjs.containsKey(key)) return (T) mObjs.get(key); + mInstantiatedObjects.add(key); return super.createDependency(key); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index af0ef8298a3f..e5ae1aacc771 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -25,11 +25,11 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; + +import static java.lang.Thread.sleep; import android.app.AppOpsManager; import android.content.pm.PackageManager; @@ -57,7 +57,6 @@ public class AppOpsControllerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = UserHandle.getUid(0, 0); private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0); - private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0); @Mock private AppOpsManager mAppOpsManager; @@ -68,8 +67,6 @@ public class AppOpsControllerTest extends SysuiTestCase { @Mock private AppOpsControllerImpl.H mMockHandler; @Mock - private PermissionFlagsCache mFlagsCache; - @Mock private DumpController mDumpController; private AppOpsControllerImpl mController; @@ -85,17 +82,9 @@ public class AppOpsControllerTest extends SysuiTestCase { // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of // TEST_UID_NON_USER_SENSITIVE are user sensitive. getContext().setMockPackageManager(mPackageManager); - when(mFlagsCache.getPermissionFlags(anyString(), anyString(), - eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn( - PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED); - when(mFlagsCache.getPermissionFlags(anyString(), anyString(), - eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn( - PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED); - when(mFlagsCache.getPermissionFlags(anyString(), anyString(), - eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0); - mController = new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mFlagsCache, - mDumpController); + mController = + new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpController); } @Test @@ -191,14 +180,6 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test - public void nonUserSensitiveOpsAreIgnored() { - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, - TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); - assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); - } - - @Test public void opNotedScheduledForRemoval() { mController.setBGHandler(mMockHandler); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, @@ -251,4 +232,100 @@ public class AppOpsControllerTest extends SysuiTestCase { // Only one post to notify subscribers verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong()); } + + @Test + public void testActiveOpNotRemovedAfterNoted() throws InterruptedException { + // Replaces the timeout delay with 5 ms + AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) { + @Override + public void scheduleRemoval(AppOpItem item, long timeToRemoval) { + super.scheduleRemoval(item, 5L); + } + }; + + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + mController.setBGHandler(testHandler); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + // Duplicates are not removed between active and noted + assertEquals(2, list.size()); + + sleep(10L); + + mTestableLooper.processAllMessages(); + + verify(mCallback, never()).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + } + + @Test + public void testNotedNotRemovedAfterActive() { + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + // Duplicates are not removed between active and noted + assertEquals(2, list.size()); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); + + mTestableLooper.processAllMessages(); + + verify(mCallback, never()).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + } + + @Test + public void testNotedAndActiveOnlyOneCall() { + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mTestableLooper.processAllMessages(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + } + + @Test + public void testActiveAndNotedOnlyOneCall() { + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mTestableLooper.processAllMessages(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt deleted file mode 100644 index dc070dea6d89..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.appops - -import android.content.Context -import android.content.pm.PackageManager -import android.os.UserHandle -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mock -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class PermissionFlagsCacheTest : SysuiTestCase() { - - companion object { - const val TEST_PERMISSION = "test_permission" - const val TEST_PACKAGE = "test_package" - } - - @Mock - private lateinit var mPackageManager: PackageManager - @Mock - private lateinit var mUserHandle: UserHandle - private lateinit var flagsCache: TestPermissionFlagsCache - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - mContext.setMockPackageManager(mPackageManager) - flagsCache = TestPermissionFlagsCache(mContext) - } - - @Test - fun testCallsPackageManager_exactlyOnce() { - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - flagsCache.time = CACHE_EXPIRATION - 1 - verify(mPackageManager).getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - } - - @Test - fun testCallsPackageManager_cacheExpired() { - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - flagsCache.time = CACHE_EXPIRATION + 1 - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - verify(mPackageManager, times(2)) - .getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - } - - @Test - fun testCallsPackageMaanger_multipleKeys() { - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - flagsCache.getPermissionFlags(TEST_PERMISSION, "", mUserHandle) - verify(mPackageManager, times(2)) - .getPermissionFlags(anyString(), anyString(), any()) - } - - private class TestPermissionFlagsCache(context: Context) : PermissionFlagsCache(context) { - var time = 0L - - override fun getCurrentTime(): Long { - return time - } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 77659df738c3..3fdbd3edfcaa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -47,6 +47,9 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; +import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; @@ -72,6 +75,7 @@ public class NotificationTestHelper { public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser()); private static final String GROUP_KEY = "gruKey"; + private static final String APP_NAME = "appName"; private final Context mContext; private int mId; @@ -303,9 +307,6 @@ public class NotificationTestHelper { null /* root */, false /* attachToRoot */); ExpandableNotificationRow row = mRow; - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setAboveShelfChangedListener(aboveShelf -> {}); final NotificationChannel channel = new NotificationChannel( @@ -329,6 +330,23 @@ public class NotificationTestHelper { entry.setRow(row); entry.createIcons(mContext, entry.getSbn()); row.setEntry(entry); + + NotificationContentInflater contentBinder = new NotificationContentInflater( + mock(NotifRemoteViewCache.class), + mock(NotificationRemoteInputManager.class)); + contentBinder.setInflateSynchronously(true); + + row.initialize( + APP_NAME, + entry.getKey(), + mock(ExpansionLogger.class), + mock(KeyguardBypassController.class), + mGroupManager, + mHeadsUpManager, + contentBinder, + mock(OnExpandClickListener.class)); + row.setAboveShelfChangedListener(aboveShelf -> { }); + row.setInflationFlags(extraInflationFlags); inflateAndWait(row); @@ -341,7 +359,6 @@ public class NotificationTestHelper { private static void inflateAndWait(ExpandableNotificationRow row) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); - row.getNotificationInflater().setInflateSynchronously(true); NotificationContentInflater.InflationCallback callback = new NotificationContentInflater.InflationCallback() { @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index cd33cf922482..cc5514f1ff68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -58,16 +58,13 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; -import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; -import com.android.systemui.ForegroundServiceController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -81,22 +78,24 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager.Keyg import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; +import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Assert; +import com.android.systemui.util.leak.LeakDetector; import org.junit.Before; import org.junit.Test; @@ -130,22 +129,16 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private HeadsUpManager mHeadsUpManager; @Mock private RankingMap mRankingMap; @Mock private RemoteInputController mRemoteInputController; - - // Dependency mocks: - @Mock private ForegroundServiceController mForegroundServiceController; + @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationGroupManager mGroupManager; @Mock private NotificationGutsManager mGutsManager; @Mock private NotificationRemoteInputManager mRemoteInputManager; - @Mock private NotificationListener mNotificationListener; @Mock private DeviceProvisionedController mDeviceProvisionedController; - @Mock private VisualStabilityManager mVisualStabilityManager; - @Mock private MetricsLogger mMetricsLogger; - @Mock private SmartReplyController mSmartReplyController; @Mock private RowInflaterTask mAsyncInflationTask; - @Mock private NotificationRowBinder mMockedRowBinder; @Mock private NotifLog mNotifLog; @Mock private FeatureFlags mFeatureFlags; + @Mock private LeakDetector mLeakDetector; private int mId; private NotificationEntry mEntry; @@ -192,21 +185,9 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mDependency.injectMockDependency(ShadeController.class); - mDependency.injectTestDependency(ForegroundServiceController.class, - mForegroundServiceController); - mDependency.injectTestDependency(NotificationLockscreenUserManager.class, - mLockscreenUserManager); - mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); - mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager); - mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager); - mDependency.injectTestDependency(NotificationListener.class, mNotificationListener); - mDependency.injectTestDependency(DeviceProvisionedController.class, - mDeviceProvisionedController); - mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); - mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); - mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController); - mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment); + if (!mDependency.hasInstantiatedDependency(SmartReplyController.class)) { + mDependency.injectMockDependency(SmartReplyController.class); + } mDependency.injectMockDependency(NotificationMediaManager.class); mCountDownLatch = new CountDownLatch(1); @@ -222,6 +203,23 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); + NotificationRowContentBinder contentBinder = new NotificationContentInflater( + mock(NotifRemoteViewCache.class), + mRemoteInputManager); + + NotificationRowBinderImpl notificationRowBinder = + new NotificationRowBinderImpl(mContext, + mRemoteInputManager, + mLockscreenUserManager, + contentBinder, + true, /* allowLongPress */ + mock(KeyguardBypassController.class), + mock(StatusBarStateController.class), + mGroupManager, + mGutsManager, + mNotificationInterruptionStateProvider, + mock(NotificationLogger.class)); + when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mEntryManager = new TestableNotificationEntryManager( @@ -237,22 +235,19 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class)), mEnvironment, - mFeatureFlags + mFeatureFlags, + () -> notificationRowBinder, + () -> mRemoteInputManager, + mLeakDetector ); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager); mEntryManager.addNotificationEntryListener(mEntryListener); mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); - NotificationRowBinderImpl notificationRowBinder = - new NotificationRowBinderImpl(mContext, true, /* allowLongPress */ - mock(KeyguardBypassController.class), - mock(StatusBarStateController.class), - mock(NotificationLogger.class)); notificationRowBinder.setUpWithPresenter( mPresenter, mListContainer, mHeadsUpManager, mBindCallback); notificationRowBinder.setInflationCallback(mEntryManager); notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); - mEntryManager.setRowBinder(notificationRowBinder); setUserSentiment( mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL); @@ -372,9 +367,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testRemoveNotification_whilePending() { - - mEntryManager.setRowBinder(mMockedRowBinder); - mEntryManager.addNotification(mSbn, mRankingMap); mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON); @@ -447,7 +439,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() { // GIVEN an entry manager with a notification - mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN a lifetime extender that always tries to extend lifetime @@ -471,7 +462,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { Assert.sMainLooper = TestableLooper.get(this).getLooper(); // GIVEN an entry manager with a notification whose life has been extended - mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.addActiveNotificationForTest(mEntry); final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender(); mEntryManager.addNotificationLifetimeExtender(extender); @@ -490,7 +480,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() { // GIVEN an entry manager with a notification whose life has been extended - mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.addActiveNotificationForTest(mEntry); NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); @@ -507,7 +496,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() { // GIVEN an entry manager with a notification - mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN two lifetime extenders, the first which never extends and the second which @@ -546,7 +534,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException { // GIVEN an entry manager with a notification - mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN interceptor that intercepts that entry @@ -568,7 +555,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { Assert.sMainLooper = TestableLooper.get(this).getLooper(); // GIVEN an entry manager with a notification - mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN interceptor that doesn't intercept diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt index 1afee120e495..29ce92074027 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt @@ -18,12 +18,15 @@ package com.android.systemui.statusbar.notification import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationRankingManager +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.NotificationGroupManager +import com.android.systemui.util.leak.LeakDetector import java.util.concurrent.CountDownLatch @@ -35,8 +38,12 @@ class TestableNotificationEntryManager( gm: NotificationGroupManager, rm: NotificationRankingManager, ke: KeyguardEnvironment, - ff: FeatureFlags -) : NotificationEntryManager(log, gm, rm, ke, ff) { + ff: FeatureFlags, + rb: dagger.Lazy<NotificationRowBinder>, + notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>, + leakDetector: LeakDetector +) : NotificationEntryManager(log, gm, rm, ke, ff, rb, + notificationRemoteInputManagerLazy, leakDetector) { public var countDownLatch: CountDownLatch = CountDownLatch(1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 28feacac8c44..09cc5ba204f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -52,9 +52,13 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; -import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; import org.junit.Before; @@ -377,7 +381,7 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); // THEN the entry is not removed - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); // THEN the entry properly records all extenders that returned true assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders); @@ -398,7 +402,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by one of them mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN the last active extender expires (but new ones become active) @@ -413,7 +417,7 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); // THEN the entry is not removed - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); // THEN the entry properly records all extenders that returned true assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders); @@ -435,7 +439,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN one (but not all) of the extenders expires @@ -443,7 +447,7 @@ public class NotifCollectionTest extends SysuiTestCase { mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); // THEN the entry is not removed - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); // THEN we don't re-query the extenders verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt()); @@ -470,7 +474,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN all of the active extenders expire @@ -480,7 +484,7 @@ public class NotifCollectionTest extends SysuiTestCase { mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2); // THEN the entry removed - assertFalse(mCollection.getNotifs().contains(entry2)); + assertFalse(mCollection.getActiveNotifs().contains(entry2)); verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false); } @@ -500,7 +504,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN the notification is reposted @@ -511,7 +515,7 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mExtender2).cancelLifetimeExtension(entry2); // THEN the notification is still present - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); } @Test(expected = IllegalStateException.class) @@ -530,7 +534,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension() @@ -559,7 +563,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN the notification is reposted diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 3e4068b6367c..be067481b779 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -38,7 +38,7 @@ import android.util.ArrayMap; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl.OnRenderListListener; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.Assert; import com.android.systemui.util.time.FakeSystemClock; @@ -72,9 +73,9 @@ import java.util.stream.Collectors; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class NotifListBuilderImplTest extends SysuiTestCase { +public class ShadeListBuilderTest extends SysuiTestCase { - private NotifListBuilderImpl mListBuilder; + private ShadeListBuilder mListBuilder; private FakeSystemClock mSystemClock = new FakeSystemClock(); @Mock private NotifLog mNotifLog; @@ -99,7 +100,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); Assert.sMainLooper = TestableLooper.get(this).getLooper(); - mListBuilder = new NotifListBuilderImpl(mSystemClock, mNotifLog); + mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog); mListBuilder.setOnRenderListListener(mOnRenderListListener); mListBuilder.attach(mNotifCollection); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java index 7ff32401b4be..5e0baf204ecf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection; +package com.android.systemui.statusbar.notification.collection.coalescer; import static com.android.internal.util.Preconditions.checkNotNull; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java index ea6c70a6e142..701cf95736ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java @@ -36,7 +36,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; @@ -65,7 +65,7 @@ public class DeviceProvisionedCoordinatorTest extends SysuiTestCase { @Mock private ActivityManagerInternal mActivityMangerInternal; @Mock private IPackageManager mIPackageManager; @Mock private DeviceProvisionedController mDeviceProvisionedController; - @Mock private NotifListBuilderImpl mNotifListBuilder; + @Mock private NotifPipeline mNotifPipeline; private Notification mNotification; private NotificationEntry mEntry; private DeviceProvisionedCoordinator mDeviceProvisionedCoordinator; @@ -84,8 +84,8 @@ public class DeviceProvisionedCoordinatorTest extends SysuiTestCase { .build(); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture()); + mDeviceProvisionedCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); mDeviceProvisionedFilter = filterCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java index 01bca0dc1078..6cc8dd908760 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java @@ -36,12 +36,11 @@ import androidx.test.filters.SmallTest; import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; import com.android.systemui.appops.AppOpsController; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import org.junit.Before; import org.junit.Test; @@ -59,8 +58,7 @@ public class ForegroundCoordinatorTest extends SysuiTestCase { @Mock private Handler mMainHandler; @Mock private ForegroundServiceController mForegroundServiceController; @Mock private AppOpsController mAppOpsController; - @Mock private NotifListBuilderImpl mNotifListBuilder; - @Mock private NotifCollection mNotifCollection; + @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; private Notification mNotification; @@ -84,9 +82,9 @@ public class ForegroundCoordinatorTest extends SysuiTestCase { ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor = ArgumentCaptor.forClass(NotifLifetimeExtender.class); - mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture()); - verify(mNotifCollection, times(1)).addNotificationLifetimeExtender( + mForegroundCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); + verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender( lifetimeExtenderCaptor.capture()); mForegroundFilter = filterCaptor.getValue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java index f921cf969e61..5866d90f62bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -41,7 +41,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; @@ -68,7 +68,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private NotifListBuilderImpl mNotifListBuilder; + @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; private KeyguardCoordinator mKeyguardCoordinator; @@ -87,8 +87,8 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { .build(); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mKeyguardCoordinator.attach(null, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture()); + mKeyguardCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreRenderFilter(filterCaptor.capture()); mKeyguardFilter = filterCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index d3b16c319692..e84f9cf352ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -33,7 +33,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; @@ -50,7 +50,7 @@ import org.mockito.MockitoAnnotations; public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; - @Mock private NotifListBuilderImpl mNotifListBuilder; + @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; private RankingCoordinator mRankingCoordinator; private NotifFilter mRankingFilter; @@ -62,8 +62,8 @@ public class RankingCoordinatorTest extends SysuiTestCase { mEntry = new NotificationEntryBuilder().build(); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mRankingCoordinator.attach(null, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture()); + mRankingCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); mRankingFilter = filterCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java new file mode 100644 index 000000000000..d7214f3b9228 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.widget.RemoteViews; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class NotifRemoteViewCacheImplTest extends SysuiTestCase { + + private NotifRemoteViewCacheImpl mNotifRemoteViewCache; + private NotificationEntry mEntry; + private NotificationEntryListener mEntryListener; + @Mock private RemoteViews mRemoteViews; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mEntry = new NotificationEntryBuilder().build(); + + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager); + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + mEntryListener = entryListenerCaptor.getValue(); + } + + @Test + public void testPutCachedView() { + // GIVEN an initialized cache for an entry. + mEntryListener.onPendingEntryAdded(mEntry); + + // WHEN a notification's cached remote views is put in. + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); + + // THEN the remote view is cached. + assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + assertEquals( + "Cached remote view is not the one we put in.", + mRemoteViews, + mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + } + + @Test + public void testRemoveCachedView() { + // GIVEN a cache with a cached view. + mEntryListener.onPendingEntryAdded(mEntry); + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); + + // WHEN we remove the cached view. + mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED); + + // THEN the remote view is not cached. + assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + } + + @Test + public void testClearCache() { + // GIVEN a non-empty cache. + mEntryListener.onPendingEntryAdded(mEntry); + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews); + + // WHEN we clear the cache. + mNotifRemoteViewCache.clearCache(mEntry); + + // THEN the cache is empty. + assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index f916fe5ad7fb..cb9da6a40cb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -22,11 +22,16 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.Notification; import android.content.Context; @@ -35,16 +40,17 @@ import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import android.util.ArrayMap; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; +import android.widget.TextView; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.InflationTask; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; @@ -57,6 +63,8 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.concurrent.CountDownLatch; @@ -73,8 +81,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { private Notification.Builder mBuilder; private ExpandableNotificationRow mRow; + @Mock private NotifRemoteViewCache mCache; + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); mBuilder = new Notification.Builder(mContext).setSmallIcon( R.drawable.ic_person) .setContentTitle("Title") @@ -83,7 +94,9 @@ public class NotificationContentInflaterTest extends SysuiTestCase { ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( mBuilder.build()); mRow = spy(row); - mNotificationInflater = new NotificationContentInflater(); + mNotificationInflater = new NotificationContentInflater( + mCache, + mock(NotificationRemoteInputManager.class)); } @Test @@ -174,7 +187,9 @@ public class NotificationContentInflaterTest extends SysuiTestCase { result, FLAG_CONTENT_VIEW_EXPANDED, 0, - new ArrayMap() /* cachedContentViews */, mRow, + mock(NotifRemoteViewCache.class), + mRow.getEntry(), + mRow, true /* isNewView */, (v, p, r) -> true, new InflationCallback() { @Override @@ -244,6 +259,71 @@ public class NotificationContentInflaterTest extends SysuiTestCase { NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView)); } + @Test + public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception { + // GIVEN a cached view. + RemoteViews contractedRemoteView = mBuilder.createContentView(); + when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(true); + when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(contractedRemoteView); + + // GIVEN existing bound view with same layout id. + View view = contractedRemoteView.apply(mContext, null /* parent */); + mRow.getPrivateLayout().setContractedChild(view); + + // WHEN inflater inflates + inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); + + // THEN the view should be re-used + assertEquals("Binder inflated a new view even though the old one was cached and usable.", + view, mRow.getPrivateLayout().getContractedChild()); + } + + @Test + public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception { + // GIVEN a cached remote view. + RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView(); + when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(true); + when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(contractedRemoteView); + + // GIVEN existing bound view with different layout id. + View view = new TextView(mContext); + mRow.getPrivateLayout().setContractedChild(view); + + // WHEN inflater inflates + inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); + + // THEN the view should be a new view + assertNotEquals("Binder (somehow) used the same view when inflating.", + view, mRow.getPrivateLayout().getContractedChild()); + } + + @Test + public void testInflationCachesCreatedRemoteView() throws Exception { + // WHEN inflater inflates + inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); + + // THEN inflater informs cache of the new remote view + verify(mCache).putCachedView( + eq(mRow.getEntry()), + eq(FLAG_CONTENT_VIEW_CONTRACTED), + any()); + } + + @Test + public void testUnbindRemovesCachedRemoteView() { + // WHEN inflated unbinds content + mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP); + + // THEN inflated informs cache to remove remote view + verify(mCache).removeCachedView( + eq(mRow.getEntry()), + eq(FLAG_CONTENT_VIEW_HEADS_UP)); + } + private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index 675b3efc5a26..84c651368dc9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -16,20 +16,17 @@ package com.android.systemui.statusbar.notification.row; -import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; -import android.graphics.drawable.Icon; import android.util.ArraySet; import android.view.NotificationHeaderView; import android.view.View; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index ea8d4ee20aab..d9939f485ce5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter; @@ -89,6 +90,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.DeviceConfigProxyFake; +import com.android.systemui.util.leak.LeakDetector; import org.junit.After; import org.junit.Before; @@ -170,7 +172,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mock(HighPriorityProvider.class) ), mock(NotificationEntryManager.KeyguardEnvironment.class), - mock(FeatureFlags.class)); + mock(FeatureFlags.class), + () -> mock(NotificationRowBinder.class), + () -> mRemoteInputManager, + mock(LeakDetector.class)); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 5ac7bfbfd296..782e14c83951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -54,7 +54,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 7e485f45e5e6..fee48522683d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -120,7 +120,8 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -213,7 +214,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private DynamicPrivacyController mDynamicPrivacyController; - @Mock private NewNotifPipeline mNewNotifPipeline; + @Mock private NotifPipelineInitializer mNewNotifPipeline; @Mock private ZenModeController mZenModeController; @Mock private AutoHideController mAutoHideController; @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager; @@ -255,6 +256,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private KeyguardDismissUtil mKeyguardDismissUtil; @Mock private ExtensionController mExtensionController; @Mock private UserInfoControllerImpl mUserInfoControllerImpl; + @Mock private NotificationRowBinderImpl mNotificationRowBinder; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -344,7 +346,6 @@ public class StatusBarTest extends SysuiTestCase { mHeadsUpManager, mDynamicPrivacyController, mBypassHeadsUpNotifier, - true, () -> mNewNotifPipeline, new FalsingManagerFake(), mBroadcastDispatcher, @@ -413,6 +414,7 @@ public class StatusBarTest extends SysuiTestCase { mKeyguardDismissUtil, mExtensionController, mUserInfoControllerImpl, + mNotificationRowBinder, mDismissCallbackRegistry); when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn( 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 c4caeb3ac531..9cb06a5d85c1 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 @@ -180,7 +180,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected void setupNetworkController() { // For now just pretend to be the data sim, so we can test that too. mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; - when(mMockTm.isDataCapable()).thenReturn(true); + when(mMockTm.isDataConnectionEnabled()).thenReturn(true); setDefaultSubId(mSubId); setSubscriptions(mSubId); mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index 95b055c7d0b5..5a5ef8bd21af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -121,7 +121,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { @Test public void testNoInternetIcon_withDefaultSub() { setupNetworkController(); - when(mMockTm.isDataCapable()).thenReturn(false); + when(mMockTm.isDataConnectionEnabled()).thenReturn(false); setupDefaultSignal(); updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0); setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false); @@ -135,7 +135,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { @Test public void testDataDisabledIcon_withDefaultSub() { setupNetworkController(); - when(mMockTm.isDataCapable()).thenReturn(false); + when(mMockTm.isDataConnectionEnabled()).thenReturn(false); setupDefaultSignal(); updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0); setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false); @@ -149,7 +149,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { @Test public void testNonDefaultSIM_showsFullSignal_connected() { setupNetworkController(); - when(mMockTm.isDataCapable()).thenReturn(false); + when(mMockTm.isDataConnectionEnabled()).thenReturn(false); setupDefaultSignal(); setDefaultSubId(mSubId + 1); updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0); @@ -164,7 +164,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { @Test public void testNonDefaultSIM_showsFullSignal_disconnected() { setupNetworkController(); - when(mMockTm.isDataCapable()).thenReturn(false); + when(mMockTm.isDataConnectionEnabled()).thenReturn(false); setupDefaultSignal(); setDefaultSubId(mSubId + 1); updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0); @@ -429,7 +429,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { @Test public void testDataDisabledIcon_UserNotSetup() { setupNetworkController(); - when(mMockTm.isDataCapable()).thenReturn(false); + when(mMockTm.isDataConnectionEnabled()).thenReturn(false); setupDefaultSignal(); updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0); setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false); @@ -444,7 +444,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { @Test public void testAlwaysShowDataRatIcon() { setupDefaultSignal(); - when(mMockTm.isDataCapable()).thenReturn(false); + when(mMockTm.isDataConnectionEnabled()).thenReturn(false); updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, TelephonyManager.NETWORK_TYPE_GSM); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index 4103d7189266..cd89d3c32697 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -30,12 +30,12 @@ import android.telephony.CellSignalStrength; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.net.DataUsageController; @@ -418,7 +418,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn); intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn); - intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId); + SubscriptionManager.putSubscriptionIdExtra(intent, mSubId); return intent; } diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml index e99c2c529bd2..5a71eb23abad 100644 --- a/packages/Tethering/AndroidManifest.xml +++ b/packages/Tethering/AndroidManifest.xml @@ -32,6 +32,7 @@ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 37e679dbeb63..ca290c637773 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -1,7 +1,152 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> <resources> <!-- OEMs that wish to change the below settings must do so via a runtime resource overlay package and *NOT* by changing this file. This file is part of the tethering mainline module. + TODO: define two resources for each config item: a default_* resource and a config_* resource, + config_* is empty by default but may be overridden by RROs. --> + <!-- List of regexpressions describing the interface (if any) that represent tetherable + USB interfaces. If the device doesn't want to support tethering over USB this should + be empty. An example would be "usb.*" --> + <string-array translatable="false" name="config_tether_usb_regexs"> + <item>"usb\\d"</item> + <item>"rndis\\d"</item> + </string-array> + + <!-- List of regexpressions describing the interface (if any) that represent tetherable + Wifi interfaces. If the device doesn't want to support tethering over Wifi this + should be empty. An example would be "softap.*" --> + <string-array translatable="false" name="config_tether_wifi_regexs"> + <item>"wlan\\d"</item> + <item>"softap\\d"</item> + </string-array> + + <!-- List of regexpressions describing the interface (if any) that represent tetherable + Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this + should be empty. An example would be "p2p-p2p.*" --> + <string-array translatable="false" name="config_tether_wifi_p2p_regexs"> + </string-array> + + <!-- List of regexpressions describing the interface (if any) that represent tetherable + bluetooth interfaces. If the device doesn't want to support tethering over bluetooth this + should be empty. --> + <string-array translatable="false" name="config_tether_bluetooth_regexs"> + <item>"bt-pan"</item> + </string-array> + + <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. --> + <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool> + + <!-- Dhcp range (min, max) to use for tethering purposes --> + <string-array translatable="false" name="config_tether_dhcp_range"> + </string-array> + + <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI, + WIFI} values allowable for tethering. + + Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or + [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE. + + This list is also modified by code within the framework, including: + + - TYPE_ETHERNET (9) is prepended to this list, and + + - the return value of TelephonyManager.isTetheringApnRequired() + determines how the array is further modified: + + * TRUE (DUN REQUIRED). + TYPE_MOBILE is removed (if present). + TYPE_MOBILE_HIPRI is removed (if present). + TYPE_MOBILE_DUN is appended (if not already present). + + * FALSE (DUN NOT REQUIRED). + TYPE_MOBILE_DUN is removed (if present). + If both of TYPE_MOBILE{,_HIPRI} are not present: + TYPE_MOBILE is appended. + TYPE_MOBILE_HIPRI is appended. + + For other changes applied to this list, now and in the future, see + com.android.server.connectivity.tethering.TetheringConfiguration. + + Note also: the order of this is important. The first upstream type + for which a satisfying network exists is used. + --> + <integer-array translatable="false" name="config_tether_upstream_types"> + </integer-array> + + <!-- When true, the tethering upstream network follows the current default + Internet network (except when the current default network is mobile, + in which case a DUN network will be used if required). + + When true, overrides the config_tether_upstream_types setting above. + --> + <bool translatable="false" name="config_tether_upstream_automatic">true</bool> + + + <!-- If the mobile hotspot feature requires provisioning, a package name and class name + can be provided to launch a supported application that provisions the devices. + EntitlementManager will send an inent to Settings with the specified package name and + class name in extras to launch provision app. + TODO: note what extras here. + + See EntitlementManager#runUiTetherProvisioning and + packages/apps/Settings/src/com/android/settings/network/TetherProvisioningActivity.java + for more details. + + For ui-less/periodic recheck support see config_mobile_hotspot_provision_app_no_ui + --> + <!-- The first element is the package name and the second element is the class name + of the provisioning app --> + <string-array translatable="false" name="config_mobile_hotspot_provision_app"> + <!-- + <item>com.example.provisioning</item> + <item>com.example.provisioning.Activity</item> + --> + </string-array> + + <!-- If the mobile hotspot feature requires provisioning, an action can be provided + that will be broadcast in non-ui cases for checking the provisioning status. + EntitlementManager will pass the specified name to Settings and Settings would + launch provisioning app by sending an intent with the package name. + + A second broadcast, action defined by config_mobile_hotspot_provision_response, + will be sent back to notify if provisioning succeeded or not. The response will + match that of the activity in config_mobile_hotspot_provision_app, but instead + contained within the int extra "EntitlementResult". + TODO: provide the system api for "EntitlementResult" extra and note it here. + + See EntitlementManager#runSilentTetherProvisioning and + packages/apps/Settings/src/com/android/settings/wifi/tether/TetherService.java for more + details. + --> + <string translatable="false" name="config_mobile_hotspot_provision_app_no_ui"></string> + + <!-- Sent in response to a provisioning check. The caller must hold the + permission android.permission.TETHER_PRIVILEGED for Settings to + receive this response. + + See config_mobile_hotspot_provision_response + --> + <string translatable="false" name="config_mobile_hotspot_provision_response"></string> + + <!-- Number of hours between each background provisioning call --> + <integer translatable="false" name="config_mobile_hotspot_provision_check_period">24</integer> + + <!-- ComponentName of the service used to run no ui tether provisioning. --> + <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string> </resources> diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml new file mode 100644 index 000000000000..e089d9d19950 --- /dev/null +++ b/packages/Tethering/res/values/overlayable.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <overlayable name="TetheringConfig"> + <policy type="product|system|vendor"> + <item type="array" name="config_tether_usb_regexs"/> + <item type="array" name="config_tether_wifi_regexs"/> + <item type="array" name="config_tether_wifi_p2p_regexs"/> + <item type="array" name="config_tether_bluetooth_regexs"/> + <item type="array" name="config_tether_dhcp_range"/> + <item type="bool" name="config_tether_enable_legacy_dhcp_server"/> + <item type="array" name="config_tether_upstream_types"/> + <item type="bool" name="config_tether_upstream_automatic"/> + <!-- Configuration values for tethering entitlement check --> + <item type="array" name="config_mobile_hotspot_provision_app"/> + <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/> + <item type="string" name="config_mobile_hotspot_provision_response"/> + <item type="integer" name="config_mobile_hotspot_provision_check_period"/> + <item type="string" name="config_wifi_tether_enable"/> + </policy> + </overlayable> +</resources> diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml index ca866a946ea2..792bce9fc334 100644 --- a/packages/Tethering/res/values/strings.xml +++ b/packages/Tethering/res/values/strings.xml @@ -1,4 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> <resources> <!-- Shown when the device is tethered --> <!-- Strings for tethered notification title [CHAR LIMIT=200] --> @@ -9,8 +23,11 @@ <!-- This notification is shown when tethering has been disabled on a user's device. The device is managed by the user's employer. Tethering can't be turned on unless the IT administrator allows it. The noun "admin" is another reference for "IT administrator." --> - <!-- Strings for tether disabling notification title [CHAR LIMIT=200] --> + <!-- Strings for tether disabling notification title [CHAR LIMIT=200] --> <string name="disable_tether_notification_title">Tethering is disabled</string> - <!-- Strings for tether disabling notification message [CHAR LIMIT=200] --> + <!-- Strings for tether disabling notification message [CHAR LIMIT=200] --> <string name="disable_tether_notification_message">Contact your admin for details</string> + + <!-- Strings for tether notification channel name [CHAR LIMIT=200] --> + <string name="notification_channel_tethering_status">Hotspot & tethering status</string> </resources>
\ No newline at end of file diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index d6abfb922a9c..038d7ae72a1a 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -50,6 +50,7 @@ import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANG import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -106,8 +107,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; @@ -663,19 +662,19 @@ public class Tethering { if (usbTethered) { if (wifiTethered || bluetoothTethered) { - showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL); + showTetheredNotification(R.drawable.stat_sys_tether_general); } else { - showTetheredNotification(SystemMessage.NOTE_TETHER_USB); + showTetheredNotification(R.drawable.stat_sys_tether_usb); } } else if (wifiTethered) { if (bluetoothTethered) { - showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL); + showTetheredNotification(R.drawable.stat_sys_tether_general); } else { /* We now have a status bar icon for WifiTethering, so drop the notification */ clearTetheredNotification(); } } else if (bluetoothTethered) { - showTetheredNotification(SystemMessage.NOTE_TETHER_BLUETOOTH); + showTetheredNotification(R.drawable.stat_sys_tether_bluetooth); } else { clearTetheredNotification(); } @@ -688,30 +687,22 @@ public class Tethering { @VisibleForTesting protected void showTetheredNotification(int id, boolean tetheringOn) { NotificationManager notificationManager = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0) + .getSystemService(Context.NOTIFICATION_SERVICE); if (notificationManager == null) { return; } - int icon = 0; - switch(id) { - case SystemMessage.NOTE_TETHER_USB: - icon = R.drawable.stat_sys_tether_usb; - break; - case SystemMessage.NOTE_TETHER_BLUETOOTH: - icon = R.drawable.stat_sys_tether_bluetooth; - break; - case SystemMessage.NOTE_TETHER_GENERAL: - default: - icon = R.drawable.stat_sys_tether_general; - break; - } + final NotificationChannel channel = new NotificationChannel( + "TETHERING_STATUS", + mContext.getResources().getString(R.string.notification_channel_tethering_status), + NotificationManager.IMPORTANCE_LOW); + notificationManager.createNotificationChannel(channel); if (mLastNotificationId != 0) { - if (mLastNotificationId == icon) { + if (mLastNotificationId == id) { return; } - notificationManager.cancelAsUser(null, mLastNotificationId, - UserHandle.ALL); + notificationManager.cancel(null, mLastNotificationId); mLastNotificationId = 0; } @@ -719,8 +710,8 @@ public class Tethering { intent.setClassName("com.android.settings", "com.android.settings.TetherSettings"); intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0, - null, UserHandle.CURRENT); + PendingIntent pi = PendingIntent.getActivity( + mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null); Resources r = mContext.getResources(); final CharSequence title; @@ -735,32 +726,31 @@ public class Tethering { } if (mTetheredNotificationBuilder == null) { - mTetheredNotificationBuilder = new Notification.Builder(mContext, - SystemNotificationChannels.NETWORK_STATUS); + mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId()); mTetheredNotificationBuilder.setWhen(0) .setOngoing(true) .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) + android.R.color.system_notification_accent_color)) .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_STATUS); } - mTetheredNotificationBuilder.setSmallIcon(icon) + mTetheredNotificationBuilder.setSmallIcon(id) .setContentTitle(title) .setContentText(message) .setContentIntent(pi); mLastNotificationId = id; - notificationManager.notifyAsUser(null, mLastNotificationId, - mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL); + notificationManager.notify(null, mLastNotificationId, + mTetheredNotificationBuilder.buildInto(new Notification())); } @VisibleForTesting protected void clearTetheredNotification() { NotificationManager notificationManager = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0) + .getSystemService(Context.NOTIFICATION_SERVICE); if (notificationManager != null && mLastNotificationId != 0) { - notificationManager.cancelAsUser(null, mLastNotificationId, - UserHandle.ALL); + notificationManager.cancel(null, mLastNotificationId); mLastNotificationId = 0; } } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java index 397ba8ada551..dbe789288c6f 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -21,7 +21,7 @@ import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static com.android.internal.R.array.config_mobile_hotspot_provision_app; import static com.android.internal.R.array.config_tether_bluetooth_regexs; @@ -33,13 +33,13 @@ import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.bool.config_tether_upstream_automatic; import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period; import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.TetheringConfigurationParcel; import android.net.util.SharedLog; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -84,6 +84,12 @@ public class TetheringConfiguration { private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"}; + /** + * Use the old dnsmasq DHCP server for tethering instead of the framework implementation. + */ + public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER = + "tether_enable_legacy_dhcp_server"; + public final String[] tetherableUsbRegexs; public final String[] tetherableWifiRegexs; public final String[] tetherableWifiP2pRegexs; @@ -122,7 +128,7 @@ public class TetheringConfiguration { legacyDhcpRanges = getLegacyDhcpRanges(res); defaultIPv4DNS = copy(DEFAULT_IPV4_DNS); - enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx); + enableLegacyDhcpServer = getEnableLegacyDhcpServer(res); provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app); provisioningAppNoUi = getProvisioningAppNoUi(res); @@ -332,10 +338,14 @@ public class TetheringConfiguration { } } - private static boolean getEnableLegacyDhcpServer(Context ctx) { - final ContentResolver cr = ctx.getContentResolver(); - final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); - return intVal != 0; + private boolean getEnableLegacyDhcpServer(final Resources res) { + return getResourceBoolean(res, config_tether_enable_legacy_dhcp_server) + || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER); + } + + @VisibleForTesting + protected boolean getDeviceConfigBoolean(final String name) { + return DeviceConfig.getBoolean(NAMESPACE_CONNECTIVITY, name, false /** defaultValue */); } private Resources getResources(Context ctx, int subId) { diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 5692a6fc5c80..2875f71e5ed2 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -23,6 +23,8 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -215,19 +217,28 @@ public class UpstreamNetworkMonitor { mLog.e("registerMobileNetworkRequest() already registered"); return; } - // The following use of the legacy type system cannot be removed until - // after upstream selection no longer finds networks by legacy type. - // See also http://b/34364553 . - final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI; - final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder() - .setCapabilities(networkCapabilitiesForType(legacyType)) - .build(); + final NetworkRequest mobileUpstreamRequest; + if (mDunRequired) { + mobileUpstreamRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_DUN) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) + .addTransportType(TRANSPORT_CELLULAR).build(); + } else { + mobileUpstreamRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_CELLULAR).build(); + } // The existing default network and DUN callbacks will be notified. // Therefore, to avoid duplicate notifications, we only register a no-op. mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST); + // The following use of the legacy type system cannot be removed until + // upstream selection no longer finds networks by legacy type. + // See also http://b/34364553 . + final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI; + // TODO: Change the timeout from 0 (no onUnavailable callback) to some // moderate callback timeout. This might be useful for updating some UI. // Additionally, we log a message to aid in any subsequent debugging. diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 66eba9ae3b7a..79bba7f6e663 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -22,10 +22,12 @@ import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,7 +41,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.util.SharedLog; @@ -49,9 +50,8 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.SystemProperties; import android.os.test.TestLooper; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.telephony.CarrierConfigManager; -import android.test.mock.MockContentResolver; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -60,7 +60,6 @@ import com.android.internal.R; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; import org.junit.After; import org.junit.Before; @@ -94,7 +93,6 @@ public final class EntitlementManagerTest { private final PersistableBundle mCarrierConfig = new PersistableBundle(); private final TestLooper mLooper = new TestLooper(); private Context mMockContext; - private MockContentResolver mContentResolver; private TestStateMachine mSM; private WrappedEntitlementManager mEnMgr; @@ -110,11 +108,6 @@ public final class EntitlementManagerTest { public Resources getResources() { return mResources; } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } } public class WrappedEntitlementManager extends EntitlementManager { @@ -151,13 +144,17 @@ public final class EntitlementManagerTest { MockitoAnnotations.initMocks(this); mMockingSession = mockitoSession() .initMocks(this) - .spyStatic(SystemProperties.class) + .mockStatic(SystemProperties.class) + .mockStatic(DeviceConfig.class) .strictness(Strictness.WARN) .startMocking(); // Don't disable tethering provisioning unless requested. doReturn(false).when( () -> SystemProperties.getBoolean( eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean())); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); when(mResources.getStringArray(R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); @@ -169,10 +166,9 @@ public final class EntitlementManagerTest { .thenReturn(new String[0]); when(mResources.getIntArray(R.array.config_tether_upstream_types)) .thenReturn(new int[0]); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); when(mLog.forSubComponent(anyString())).thenReturn(mLog); - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mMockContext = new MockContext(mContext); mSM = new TestStateMachine(); mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE); diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java index 7799da4b94a7..ef97ad418245 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java @@ -21,40 +21,44 @@ import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.internal.R.array.config_mobile_hotspot_provision_app; import static com.android.internal.R.array.config_tether_bluetooth_regexs; import static com.android.internal.R.array.config_tether_dhcp_range; import static com.android.internal.R.array.config_tether_upstream_types; import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.util.SharedLog; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.telephony.TelephonyManager; -import android.test.mock.MockContentResolver; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; +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 org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.Arrays; import java.util.Iterator; @@ -69,9 +73,10 @@ public class TetheringConfigurationTest { @Mock private TelephonyManager mTelephonyManager; @Mock private Resources mResources; @Mock private Resources mResourcesForSubId; - private MockContentResolver mContentResolver; private Context mMockContext; private boolean mHasTelephonyManager; + private boolean mEnableLegacyDhcpServer; + private MockitoSession mMockingSession; private class MockTetheringConfiguration extends TetheringConfiguration { MockTetheringConfiguration(Context ctx, SharedLog log, int id) { @@ -101,16 +106,20 @@ public class TetheringConfigurationTest { } return super.getSystemService(name); } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } } @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + // TODO: use a dependencies class instead of mock statics. + mMockingSession = mockitoSession() + .initMocks(this) + .mockStatic(DeviceConfig.class) + .strictness(Strictness.WARN) + .startMocking(); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]); when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]); when(mResources.getStringArray(config_tether_wifi_regexs)) @@ -119,10 +128,15 @@ public class TetheringConfigurationTest { when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]); when(mResources.getStringArray(config_mobile_hotspot_provision_app)) .thenReturn(new String[0]); - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); mHasTelephonyManager = true; mMockContext = new MockContext(mContext); + mEnableLegacyDhcpServer = false; + } + + @After + public void tearDown() throws Exception { + mMockingSession.finishMocking(); } private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) { @@ -268,19 +282,35 @@ public class TetheringConfigurationTest { @Test public void testNewDhcpServerDisabled() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); - - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertTrue(cfg.enableLegacyDhcpServer); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + + final TetheringConfiguration enableByRes = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertTrue(enableByRes.enableLegacyDhcpServer); + + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + doReturn(true).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + + final TetheringConfiguration enableByDevConfig = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertTrue(enableByDevConfig.enableLegacyDhcpServer); } @Test public void testNewDhcpServerEnabled() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + + final TetheringConfiguration cfg = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertFalse(cfg.enableLegacyDhcpServer); } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 7af48a89d87c..900c67198677 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -35,9 +35,10 @@ import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -224,6 +225,11 @@ public class TetheringTest { if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE; return super.getSystemServiceName(serviceClass); } + + @Override + public Context createContextAsUser(UserHandle user, int flags) { + return mContext; + } } public class MockIpServerDependencies extends IpServer.Dependencies { @@ -265,6 +271,11 @@ public class TetheringTest { } @Override + protected boolean getDeviceConfigBoolean(final String name) { + return false; + } + + @Override protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) { return mResources; } @@ -423,6 +434,7 @@ public class TetheringTest { .thenReturn(new int[0]); when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic)) .thenReturn(false); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); when(mNetd.interfaceGetList()) .thenReturn(new String[] { TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME}); @@ -432,9 +444,9 @@ public class TetheringTest { .thenReturn(true); mServiceContext = new TestContext(mContext); + when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null); mContentResolver = new MockContentResolver(mServiceContext); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); mIntents = new Vector<>(); mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -489,13 +501,15 @@ public class TetheringTest { p2pInfo.groupFormed = isGroupFormed; p2pInfo.isGroupOwner = isGroupOwner; - WifiP2pGroup group = new WifiP2pGroup(); - group.setIsGroupOwner(isGroupOwner); - group.setInterface(ifname); + WifiP2pGroup group = mock(WifiP2pGroup.class); + when(group.isGroupOwner()).thenReturn(isGroupOwner); + when(group.getInterface()).thenReturn(ifname); + + final Intent intent = mock(Intent.class); + when(intent.getAction()).thenReturn(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); + when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO)).thenReturn(p2pInfo); + when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)).thenReturn(group); - final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); - intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo); - intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group); mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL, P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST); } @@ -686,7 +700,7 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_IPv4LegacyDhcp() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true); sendConfigurationChanged(); final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); runUsbTethering(upstreamState); diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp index 2a94237f2aed..61f8143e68b5 100644 --- a/packages/services/PacProcessor/jni/Android.bp +++ b/packages/services/PacProcessor/jni/Android.bp @@ -37,4 +37,10 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], + sanitize: { + cfi: true, + diag: { + cfi: true, + }, + }, } diff --git a/proto/Android.bp b/proto/Android.bp index 65bccbb4aac8..7cf6ce740969 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -27,3 +27,8 @@ java_library_static { srcs: ["src/metrics_constants/metrics_constants.proto"], sdk_version: "system_current", } + +filegroup { + name: "system-messages-proto-src", + srcs: ["src/system_messages.proto"], +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index cbff6bdcec77..37ac3ec6f105 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -35,6 +35,7 @@ import android.provider.Settings; import android.util.Slog; import android.view.Display; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -264,6 +265,24 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } @Override + public boolean switchToInputMethod(String imeId) { + synchronized (mLock) { + if (!hasRightsToCurrentUserLocked()) { + return false; + } + } + final boolean result; + final int callingUserId = UserHandle.getCallingUserId(); + final long identity = Binder.clearCallingIdentity(); + try { + result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + return result; + } + + @Override public boolean isAccessibilityButtonAvailable() { synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 7dd4a7089954..7a8a112fae40 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -297,6 +297,11 @@ class UiAutomationManager { } @Override + public boolean switchToInputMethod(String imeId) { + return false; + } + + @Override public boolean isAccessibilityButtonAvailable() { return false; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 03d9626cab91..5405fc74dfb6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -331,8 +331,8 @@ public final class AutofillManagerService } @Override // from SystemService - public boolean isSupported(UserInfo userInfo) { - return userInfo.isFull() || userInfo.isManagedProfile(); + public boolean isSupportedUser(TargetUser user) { + return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile(); } @Override // from SystemService diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index f34b5e71ad7b..7d354d20cb67 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -162,6 +162,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** uid the session is for */ public final int uid; + /** user id the session is for */ + public final int userId; + /** ID of the task associated with this session's activity */ public final int taskId; @@ -613,7 +616,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState int newState, int flags) { if (isInlineSuggestionsEnabled()) { mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId, mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); } @@ -759,6 +762,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFlags = flags; this.taskId = taskId; this.uid = uid; + this.userId = userId; mStartTime = SystemClock.elapsedRealtime(); mService = service; mLock = lock; diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index d45a54e0ff28..b13bef2de151 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1406,10 +1406,7 @@ public class BackupManagerService extends IBackupManager.Stub { long oldId = Binder.clearCallingIdentity(); final int[] userIds; try { - userIds = - mContext - .getSystemService(UserManager.class) - .getProfileIds(callingUserId, false); + userIds = getUserManager().getProfileIds(callingUserId, false); } finally { Binder.restoreCallingIdentity(oldId); } diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index eb6262094849..b06fc52a24c2 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -21,12 +21,10 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.TAG; import android.app.backup.RestoreSet; -import android.content.Intent; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.RemoteException; -import android.os.UserHandle; import android.util.EventLog; import android.util.Pair; import android.util.Slog; @@ -40,7 +38,6 @@ import com.android.server.backup.DataChangedJournal; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.fullbackup.PerformAdbBackupTask; -import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.keyvalue.BackupRequest; import com.android.server.backup.keyvalue.KeyValueBackupTask; import com.android.server.backup.params.AdbBackupParams; @@ -73,10 +70,7 @@ public class BackupHandler extends Handler { public static final int MSG_RESTORE_SESSION_TIMEOUT = 8; public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9; public static final int MSG_RUN_ADB_RESTORE = 10; - public static final int MSG_RETRY_INIT = 11; public static final int MSG_RETRY_CLEAR = 12; - public static final int MSG_WIDGET_BROADCAST = 13; - public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14; public static final int MSG_REQUEST_BACKUP = 15; public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16; public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17; @@ -279,12 +273,6 @@ public class BackupHandler extends Handler { break; } - case MSG_RUN_FULL_TRANSPORT_BACKUP: { - PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj; - (new Thread(task, "transport-backup")).start(); - break; - } - case MSG_RUN_RESTORE: { RestoreParams params = (RestoreParams) msg.obj; Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); @@ -445,12 +433,6 @@ public class BackupHandler extends Handler { break; } - case MSG_WIDGET_BROADCAST: { - final Intent intent = (Intent) msg.obj; - backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM); - break; - } - case MSG_REQUEST_BACKUP: { BackupParams params = (BackupParams) msg.obj; if (MORE_DEBUG) { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 27824afb8d46..8eca62a4932b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -175,8 +175,8 @@ public final class ContentCaptureManagerService extends } @Override // from SystemService - public boolean isSupported(UserInfo userInfo) { - return userInfo.isFull() || userInfo.isManagedProfile(); + public boolean isSupportedUser(TargetUser user) { + return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile(); } @Override // from SystemService diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 312dd46fbc73..368416b0d4a1 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -41,6 +41,7 @@ import com.android.server.pm.PackageList; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; import java.util.List; import java.util.function.Consumer; @@ -478,10 +479,12 @@ public abstract class PackageManagerInternal { * will be disabled. Pass in null or an empty list to disable * all overlays. The order of the items is significant if several * overlays modify the same resource. + * @param outUpdatedPackageNames An output list that contains the package names of packages + * affected by the update of enabled overlays. * @return true if all packages names were known by the package manager, false otherwise */ public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName, - List<String> overlayPackageNames); + List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames); /** * Resolves an activity intent, allowing instant apps to be resolved. @@ -811,6 +814,12 @@ public abstract class PackageManagerInternal { public abstract boolean isApexPackage(String packageName); /** + * Returns list of {@code packageName} of apks inside the given apex. + * @param apexPackageName Package name of the apk container of apex + */ + public abstract List<String> getApksInApex(String apexPackageName); + + /** * Uninstalls given {@code packageName}. * * @param packageName apex package to uninstall. diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 73b6c7a570ed..0f2fb9252c29 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -208,6 +208,7 @@ class AlarmManagerService extends SystemService { AppWakeupHistory mAppWakeupHistory; ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); + IBinder.DeathRecipient mListenerDeathRecipient; Intent mTimeTickIntent; IAlarmListener mTimeTickTrigger; PendingIntent mDateChangeSender; @@ -1447,6 +1448,18 @@ class AlarmManagerService extends SystemService { public void onStart() { mInjector.init(); + mListenerDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + } + + @Override + public void binderDied(IBinder who) { + final IAlarmListener listener = IAlarmListener.Stub.asInterface(who); + removeImpl(null, listener); + } + }; + synchronized (mLock) { mHandler = new AlarmHandler(); mConstants = new Constants(mHandler); @@ -1653,6 +1666,15 @@ class AlarmManagerService extends SystemService { return; } + if (directReceiver != null) { + try { + directReceiver.asBinder().linkToDeath(mListenerDeathRecipient, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Dropping unreachable alarm listener " + listenerTag); + return; + } + } + // Sanity check the window length. This will catch people mistakenly // trying to pass an end-of-window timestamp rather than a duration. if (windowLength > AlarmManager.INTERVAL_HALF_DAY) { @@ -2079,8 +2101,9 @@ class AlarmManagerService extends SystemService { @Override public long currentNetworkTimeMillis() { final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext()); - if (time.hasCache()) { - return time.currentTimeMillis(); + NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult(); + if (ntpResult != null) { + return ntpResult.currentTimeMillis(); } else { throw new ParcelableException(new DateTimeException("Missing NTP fix")); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c2e32d332f50..5435cbad870f 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -84,12 +84,12 @@ import android.net.MatchAllNetworkSpecifier; import android.net.NattSocketKeepalive; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkConfig; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkMisc; import android.net.NetworkMonitorManager; import android.net.NetworkPolicyManager; import android.net.NetworkProvider; @@ -364,10 +364,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_PROXY_HAS_CHANGED = 16; /** - * used internally when registering NetworkFactories - * obj = NetworkFactoryInfo + * used internally when registering NetworkProviders + * obj = NetworkProviderInfo */ - private static final int EVENT_REGISTER_NETWORK_FACTORY = 17; + private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17; /** * used internally when registering NetworkAgents @@ -403,10 +403,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_RELEASE_NETWORK_REQUEST = 22; /** - * used internally when registering NetworkFactories + * used internally when registering NetworkProviders * obj = Messenger */ - private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23; + private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23; /** * used internally to expire a wakelock when transitioning @@ -2394,9 +2394,9 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - pw.print("NetworkFactories for:"); - for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - pw.print(" " + nfi.name); + pw.print("NetworkProviders for:"); + for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + pw.print(" " + npi.name); } pw.println(); pw.println(); @@ -2631,8 +2631,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai.everConnected) { loge("ERROR: cannot call explicitlySelected on already-connected network"); } - nai.networkMisc.explicitlySelected = toBool(msg.arg1); - nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2); + nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1); + nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2); // Mark the network as temporarily accepting partial connectivity so that it // will be validated (and possibly become default) even if it only provides // partial internet access. Note that if user connects to partial connectivity @@ -2640,7 +2640,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // out of wifi coverage) and if the same wifi is available again, the device // will auto connect to this wifi even though the wifi has "no internet". // TODO: Evaluate using a separate setting in IpMemoryStore. - nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2); + nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2); break; } case NetworkAgent.EVENT_SOCKET_KEEPALIVE: { @@ -2672,10 +2672,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Only show the notification when the private DNS is broken and the // PRIVATE_DNS_BROKEN notification hasn't shown since last valid. - if (privateDnsBroken && !nai.networkMisc.hasShownBroken) { + if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) { showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN); } - nai.networkMisc.hasShownBroken = privateDnsBroken; + nai.networkAgentConfig.hasShownBroken = privateDnsBroken; } else if (nai.networkCapabilities.isPrivateDnsBroken()) { // If probePrivateDnsCompleted is false but nai.networkCapabilities says // private DNS is broken, it means this network is being reevaluated. @@ -2685,7 +2685,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkCapabilities.setPrivateDnsBroken(false); final int oldScore = nai.getCurrentScore(); updateCapabilities(oldScore, nai, nai.networkCapabilities); - nai.networkMisc.hasShownBroken = false; + nai.networkAgentConfig.hasShownBroken = false; } break; } @@ -2727,7 +2727,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.lastValidated = valid; nai.everValidated |= valid; updateCapabilities(oldScore, nai, nai.networkCapabilities); - // If score has changed, rebroadcast to NetworkFactories. b/17726566 + // If score has changed, rebroadcast to NetworkProviders. b/17726566 if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); if (valid) { handleFreshlyValidatedNetwork(nai); @@ -2744,7 +2744,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If network becomes valid, the hasShownBroken should be reset for // that network so that the notification will be fired when the private // DNS is broken again. - nai.networkMisc.hasShownBroken = false; + nai.networkAgentConfig.hasShownBroken = false; } } else if (partialConnectivityChanged) { updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); @@ -2803,9 +2803,10 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); break; } - if (!nai.networkMisc.provisioningNotificationDisabled) { + if (!nai.networkAgentConfig.provisioningNotificationDisabled) { mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null, - (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected); + (PendingIntent) msg.obj, + nai.networkAgentConfig.explicitlySelected); } } break; @@ -2842,6 +2843,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } + // TODO: delete when direct use of registerNetworkFactory is no longer supported. private boolean maybeHandleNetworkFactoryMessage(Message msg) { switch (msg.what) { default: @@ -3031,16 +3033,16 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleAsyncChannelHalfConnect(Message msg) { ensureRunningOnConnectivityServiceThread(); final AsyncChannel ac = (AsyncChannel) msg.obj; - if (mNetworkFactoryInfos.containsKey(msg.replyTo)) { + if (mNetworkProviderInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection - NetworkFactoryInfo nfi = mNetworkFactoryInfos.get(msg.replyTo); - nfi.completeConnection(); - sendAllRequestsToFactory(nfi); + NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo); + npi.completeConnection(); + sendAllRequestsToProvider(npi); } else { loge("Error connecting NetworkFactory"); - mNetworkFactoryInfos.remove(msg.obj); + mNetworkProviderInfos.remove(msg.obj); } } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { @@ -3072,8 +3074,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai != null) { disconnectAndDestroyNetwork(nai); } else { - NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo); - if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name); + NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo); + if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name); } } @@ -3156,7 +3158,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it // after we've rematched networks with requests which should make a potential // fallback network the default or requested a new network from the - // NetworkFactories, so network traffic isn't interrupted for an unnecessarily + // NetworkProviders, so network traffic isn't interrupted for an unnecessarily // long time. destroyNativeNetwork(nai); mDnsManager.removeNetwork(nai.network); @@ -3169,8 +3171,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // This should never fail. Specifying an already in use NetID will cause failure. if (networkAgent.isVPN()) { mNetd.networkCreateVpn(networkAgent.network.netId, - (networkAgent.networkMisc == null - || !networkAgent.networkMisc.allowBypass)); + (networkAgent.networkAgentConfig == null + || !networkAgent.networkAgentConfig.allowBypass)); } else { mNetd.networkCreatePhysical(networkAgent.network.netId, getNetworkPermission(networkAgent.networkCapabilities)); @@ -3419,8 +3421,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.cancelRequest(nri.request); + for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + npi.cancelRequest(nri.request); } } else { // listens don't have a singular affectedNetwork. Check all networks to see @@ -3470,16 +3472,16 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - if (!nai.networkMisc.explicitlySelected) { + if (!nai.networkAgentConfig.explicitlySelected) { Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network"); } - if (accept != nai.networkMisc.acceptUnvalidated) { - nai.networkMisc.acceptUnvalidated = accept; + if (accept != nai.networkAgentConfig.acceptUnvalidated) { + nai.networkAgentConfig.acceptUnvalidated = accept; // If network becomes partial connectivity and user already accepted to use this // network, we should respect the user's option and don't need to popup the // PARTIAL_CONNECTIVITY notification to user again. - nai.networkMisc.acceptPartialConnectivity = accept; + nai.networkAgentConfig.acceptPartialConnectivity = accept; rematchAllNetworksAndRequests(); sendUpdatedScoreToFactories(nai); } @@ -3516,8 +3518,8 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - if (accept != nai.networkMisc.acceptPartialConnectivity) { - nai.networkMisc.acceptPartialConnectivity = accept; + if (accept != nai.networkAgentConfig.acceptPartialConnectivity) { + nai.networkAgentConfig.acceptPartialConnectivity = accept; } // TODO: Use the current design or save the user choice into IpMemoryStore. @@ -3615,14 +3617,29 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceSettingsPermission(); } + final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork); + if (nm == null) return; + nm.notifyCaptivePortalAppFinished(response); + } + + @Override + public void appRequest(final int request) { + final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork); + if (nm == null) return; + + if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) { + nm.forceReevaluation(Binder.getCallingUid()); + } + } + + @Nullable + private NetworkMonitorManager getNetworkMonitorManager(final Network network) { // getNetworkAgentInfoForNetwork is thread-safe - final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork); - if (nai == null) return; + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai == null) return null; // nai.networkMonitor() is thread-safe - final NetworkMonitorManager nm = nai.networkMonitor(); - if (nm == null) return; - nm.notifyCaptivePortalAppFinished(response); + return nai.networkMonitor(); } @Override @@ -3727,7 +3744,7 @@ public class ConnectivityService extends IConnectivityManager.Stub action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY; // Don't bother the user with a high-priority notification if the network was not // explicitly selected by the user. - highPriority = nai.networkMisc.explicitlySelected; + highPriority = nai.networkAgentConfig.explicitlySelected; break; default: Slog.wtf(TAG, "Unknown notification type " + type); @@ -3760,14 +3777,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // automatically connects to a network that has partial Internet access, the user will // always be able to use it, either because they've already chosen "don't ask again" or // because we have prompt them. - if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) { + if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) { return true; } // If a network has no Internet access, only prompt if the network was explicitly selected // and if the user has not already told us to use the network regardless of whether it // validated or not. - if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) { + if (nai.networkAgentConfig.explicitlySelected + && !nai.networkAgentConfig.acceptUnvalidated) { return true; } @@ -3855,12 +3873,12 @@ public class ConnectivityService extends IConnectivityManager.Stub handleApplyDefaultProxy((ProxyInfo)msg.obj); break; } - case EVENT_REGISTER_NETWORK_FACTORY: { - handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj); + case EVENT_REGISTER_NETWORK_PROVIDER: { + handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj); break; } - case EVENT_UNREGISTER_NETWORK_FACTORY: { - handleUnregisterNetworkFactory((Messenger)msg.obj); + case EVENT_UNREGISTER_NETWORK_PROVIDER: { + handleUnregisterNetworkProvider((Messenger) msg.obj); break; } case EVENT_REGISTER_NETWORK_AGENT: { @@ -4905,7 +4923,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } }; - private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>(); + private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>(); private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; @@ -4913,18 +4931,18 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mUidToNetworkRequestCount") private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray(); - private static class NetworkFactoryInfo { + private static class NetworkProviderInfo { public final String name; public final Messenger messenger; private final AsyncChannel mAsyncChannel; private final IBinder.DeathRecipient mDeathRecipient; - public final int factorySerialNumber; + public final int providerId; - NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel, - int factorySerialNumber, IBinder.DeathRecipient deathRecipient) { + NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel, + int providerId, IBinder.DeathRecipient deathRecipient) { this.name = name; this.messenger = messenger; - this.factorySerialNumber = factorySerialNumber; + this.providerId = providerId; mAsyncChannel = asyncChannel; mDeathRecipient = deathRecipient; @@ -4942,17 +4960,17 @@ public class ConnectivityService extends IConnectivityManager.Stub messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj)); } catch (RemoteException e) { // Remote process died. Ignore; the death recipient will remove this - // NetworkFactoryInfo from mNetworkFactoryInfos. + // NetworkProviderInfo from mNetworkProviderInfos. } } - void requestNetwork(NetworkRequest request, int score, int servingSerialNumber) { + void requestNetwork(NetworkRequest request, int score, int servingProviderId) { if (isLegacyNetworkFactory()) { mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, - servingSerialNumber, request); + servingProviderId, request); } else { sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, - servingSerialNumber, request); + servingProviderId, request); } } @@ -5365,45 +5383,45 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public int registerNetworkFactory(Messenger messenger, String name) { enforceNetworkFactoryPermission(); - NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(), + NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(), nextNetworkProviderId(), null /* deathRecipient */); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi)); - return nfi.factorySerialNumber; + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); + return npi.providerId; } - private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) { - if (mNetworkFactoryInfos.containsKey(nfi.messenger)) { + private void handleRegisterNetworkProvider(NetworkProviderInfo npi) { + if (mNetworkProviderInfos.containsKey(npi.messenger)) { // Avoid creating duplicates. even if an app makes a direct AIDL call. // This will never happen if an app calls ConnectivityManager#registerNetworkProvider, // as that will throw if a duplicate provider is registered. - Slog.e(TAG, "Attempt to register existing NetworkFactoryInfo " - + mNetworkFactoryInfos.get(nfi.messenger).name); + Slog.e(TAG, "Attempt to register existing NetworkProviderInfo " + + mNetworkProviderInfos.get(npi.messenger).name); return; } - if (DBG) log("Got NetworkFactory Messenger for " + nfi.name); - mNetworkFactoryInfos.put(nfi.messenger, nfi); - nfi.connect(mContext, mTrackerHandler); - if (!nfi.isLegacyNetworkFactory()) { + if (DBG) log("Got NetworkProvider Messenger for " + npi.name); + mNetworkProviderInfos.put(npi.messenger, npi); + npi.connect(mContext, mTrackerHandler); + if (!npi.isLegacyNetworkFactory()) { // Legacy NetworkFactories get their requests when their AsyncChannel connects. - sendAllRequestsToFactory(nfi); + sendAllRequestsToProvider(npi); } } @Override public int registerNetworkProvider(Messenger messenger, String name) { enforceNetworkFactoryPermission(); - NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, + NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, null /* asyncChannel */, nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger)); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi)); - return nfi.factorySerialNumber; + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); + return npi.providerId; } @Override public void unregisterNetworkProvider(Messenger messenger) { enforceNetworkFactoryPermission(); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger)); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger)); } @Override @@ -5411,13 +5429,13 @@ public class ConnectivityService extends IConnectivityManager.Stub unregisterNetworkProvider(messenger); } - private void handleUnregisterNetworkFactory(Messenger messenger) { - NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger); - if (nfi == null) { - loge("Failed to find Messenger in unregisterNetworkFactory"); + private void handleUnregisterNetworkProvider(Messenger messenger) { + NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger); + if (npi == null) { + loge("Failed to find Messenger in unregisterNetworkProvider"); return; } - if (DBG) log("unregisterNetworkFactory for " + nfi.name); + if (DBG) log("unregisterNetworkProvider for " + npi.name); } @Override @@ -5487,11 +5505,14 @@ public class ConnectivityService extends IConnectivityManager.Stub // changes that would conflict throughout the automerger graph. Having this method temporarily // helps with the process of going through with all these dependent changes across the entire // tree. - public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, + /** + * Register a new agent. {@see #registerNetworkAgent} below. + */ + public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - int currentScore, NetworkMisc networkMisc) { + int currentScore, NetworkAgentConfig networkAgentConfig) { return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities, - currentScore, networkMisc, NetworkFactory.SerialNumber.NONE); + currentScore, networkAgentConfig, NetworkProvider.ID_NONE); } /** @@ -5506,12 +5527,13 @@ public class ConnectivityService extends IConnectivityManager.Stub * later : see {@link #updateCapabilities}. * @param currentScore the initial score of the network. See * {@link NetworkAgentInfo#getCurrentScore}. - * @param networkMisc metadata about the network. This is never updated. - * @param factorySerialNumber the serial number of the factory owning this NetworkAgent. + * @param networkAgentConfig metadata about the network. This is never updated. + * @param providerId the ID of the provider owning this NetworkAgent. + * @return the network created for this agent. */ - public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, + public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - int currentScore, NetworkMisc networkMisc, int factorySerialNumber) { + int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) { enforceNetworkFactoryPermission(); LinkProperties lp = new LinkProperties(linkProperties); @@ -5523,8 +5545,8 @@ public class ConnectivityService extends IConnectivityManager.Stub ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore); final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, - ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, - mDnsResolver, mNMS, factorySerialNumber); + ns, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this, + mNetd, mDnsResolver, mNMS, providerId); // Make sure the network capabilities reflect what the agent info says. nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc)); final String extraInfo = networkInfo.getExtraInfo(); @@ -5542,7 +5564,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If the network disconnects or sends any other event before that, messages are deferred by // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the // registration. - return nai.network.netId; + return nai.network; } private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) { @@ -5803,7 +5825,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Once a NetworkAgent is connected, complain if some immutable capabilities are removed. // Don't complain for VPNs since they're not driven by requests and there is no risk of // causing a connect/teardown loop. - // TODO: remove this altogether and make it the responsibility of the NetworkFactories to + // TODO: remove this altogether and make it the responsibility of the NetworkProviders to // avoid connect/teardown loops. if (nai.everConnected && !nai.isVPN() && @@ -5945,7 +5967,7 @@ public class ConnectivityService extends IConnectivityManager.Stub LinkProperties lp) { if (nc == null || lp == null) return false; return nai.isVPN() - && !nai.networkMisc.allowBypass + && !nai.networkAgentConfig.allowBypass && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID && lp.getInterfaceName() != null && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); @@ -6043,13 +6065,13 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG){ log("sending new Min Network Score(" + score + "): " + networkRequest.toString()); } - for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.requestNetwork(networkRequest, score, serial); + for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + npi.requestNetwork(networkRequest, score, serial); } } /** Sends all current NetworkRequests to the specified factory. */ - private void sendAllRequestsToFactory(NetworkFactoryInfo nfi) { + private void sendAllRequestsToProvider(NetworkProviderInfo npi) { ensureRunningOnConnectivityServiceThread(); for (NetworkRequestInfo nri : mNetworkRequests.values()) { if (nri.request.isListen()) continue; @@ -6061,9 +6083,9 @@ public class ConnectivityService extends IConnectivityManager.Stub serial = nai.factorySerialNumber; } else { score = 0; - serial = NetworkFactory.SerialNumber.NONE; + serial = NetworkProvider.ID_NONE; } - nfi.requestNetwork(nri.request, score, serial); + npi.requestNetwork(nri.request, score, serial); } } @@ -6344,11 +6366,11 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); } addedRequests.add(nri); - // Tell NetworkFactories about the new score, so they can stop + // Tell NetworkProviders about the new score, so they can stop // trying to connect if they know they cannot match it. // TODO - this could get expensive if we have a lot of requests for this // network. Think about if there is a way to reduce this. Push - // netid->request mapping to each factory? + // netid->request mapping to each provider? sendUpdatedScoreToFactories(nri.request, newSatisfier); if (isDefaultRequest(nri)) { isNewDefault = true; @@ -6377,7 +6399,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " + newNetwork.name() + - " without updating mSatisfier or factories!"); + " without updating mSatisfier or providers!"); } // TODO: Technically, sending CALLBACK_LOST here is // incorrect if there is a replacement network currently @@ -6636,7 +6658,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // command must be sent after updating LinkProperties to maximize chances of // NetworkMonitor seeing the correct LinkProperties when starting. // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call. - if (networkAgent.networkMisc.acceptPartialConnectivity) { + if (networkAgent.networkAgentConfig.acceptPartialConnectivity) { networkAgent.networkMonitor().setAcceptPartialConnectivity(); } networkAgent.networkMonitor().notifyNetworkConnected( diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java index 70569db5e2d3..5179fa7a6eb5 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -38,11 +38,13 @@ import android.view.IGraphicsStats; import android.view.IGraphicsStatsCallback; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FastPrintWriter; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -78,6 +80,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private static final int SAVE_BUFFER = 1; private static final int DELETE_OLD = 2; + private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever. + // This isn't static because we need this to happen after registerNativeMethods, however // the class is loaded (and thus static ctor happens) before that occurs. private final int ASHMEM_SIZE = nGetAshmemSize(); @@ -121,6 +125,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return true; } }); + nativeInit(); } /** @@ -186,6 +191,86 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return pfd; } + // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period + // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the + // current day. + // This method is invoked from native code only. + @SuppressWarnings({"UnusedDeclaration"}) + private long pullGraphicsStats(boolean lastFullDay) throws RemoteException { + int uid = Binder.getCallingUid(); + + // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method. + // TODO: remove exception for statsd daemon after required permissions are granted. statsd + // TODO: should have these permissions granted by data/etc/platform.xml, but it does not. + if (uid != AID_STATSD) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { + pw.flush(); + throw new RemoteException(sw.toString()); + } + } + + long callingIdentity = Binder.clearCallingIdentity(); + try { + return pullGraphicsStatsImpl(lastFullDay); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + private long pullGraphicsStatsImpl(boolean lastFullDay) { + long targetDay; + if (lastFullDay) { + // Get stats from yesterday. Stats stay constant, because the day is over. + targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis(); + } else { + // Get stats from today. Stats may change as more apps are run today. + targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis(); + } + + // Find active buffers for targetDay. + ArrayList<HistoricalBuffer> buffers; + synchronized (mLock) { + buffers = new ArrayList<>(mActive.size()); + for (int i = 0; i < mActive.size(); i++) { + ActiveBuffer buffer = mActive.get(i); + if (buffer.mInfo.startTime == targetDay) { + try { + buffers.add(new HistoricalBuffer(buffer)); + } catch (IOException ex) { + // Ignore + } + } + } + } + + // Dump active and historic buffers for targetDay in a serialized + // GraphicsStatsServiceDumpProto proto. + long dump = nCreateDump(-1, true); + try { + synchronized (mFileAccessLock) { + HashSet<File> skipList = dumpActiveLocked(dump, buffers); + buffers.clear(); + String subPath = String.format("%d", targetDay); + File dateDir = new File(mGraphicsStatsDir, subPath); + if (dateDir.exists()) { + for (File pkg : dateDir.listFiles()) { + for (File version : pkg.listFiles()) { + File data = new File(version, "total"); + if (skipList.contains(data)) { + continue; + } + nAddToDump(dump, data.getAbsolutePath()); + } + } + } + } + } finally { + return nFinishDumpInMemory(dump); + } + } + private ParcelFileDescriptor getPfd(MemoryFile file) { try { if (!file.getFileDescriptor().valid()) { @@ -379,12 +464,21 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } + @Override + protected void finalize() throws Throwable { + nativeDestructor(); + } + + private native void nativeInit(); + private static native void nativeDestructor(); + private static native int nGetAshmemSize(); private static native long nCreateDump(int outFd, boolean isProto); private static native void nAddToDump(long dump, String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); private static native void nAddToDump(long dump, String path); private static native void nFinishDump(long dump); + private static native long nFinishDumpInMemory(long dump); private static native void nSaveBuffer(String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 32128d5f26f8..dc393d1609de 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -69,7 +69,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; -import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.stats.location.LocationStatsEnums; @@ -84,7 +83,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -105,6 +103,7 @@ import com.android.server.location.LocationUsageLogger; import com.android.server.location.MockProvider; import com.android.server.location.MockableLocationProvider; import com.android.server.location.PassiveProvider; +import com.android.server.location.UserInfoStore; import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.io.ByteArrayOutputStream; @@ -194,6 +193,7 @@ public class LocationManagerService extends ILocationManager.Stub { private final Object mLock = new Object(); private final Context mContext; private final Handler mHandler; + private final UserInfoStore mUserInfoStore; private final LocationSettingsStore mSettingsStore; private final LocationUsageLogger mLocationUsageLogger; @@ -203,7 +203,6 @@ public class LocationManagerService extends ILocationManager.Stub { private PackageManager mPackageManager; private PowerManager mPowerManager; private ActivityManager mActivityManager; - private UserManager mUserManager; private GeofenceManager mGeofenceManager; private LocationFudger mLocationFudger; @@ -237,10 +236,6 @@ public class LocationManagerService extends ILocationManager.Stub { private final HashMap<String, Location> mLastLocationCoarseInterval = new HashMap<>(); - // current active user on the device - private int mCurrentUserId; - private int[] mCurrentUserProfiles; - @GuardedBy("mLock") @PowerManager.LocationPowerSaveMode private int mBatterySaverMode; @@ -248,12 +243,10 @@ public class LocationManagerService extends ILocationManager.Stub { private LocationManagerService(Context context) { mContext = context; mHandler = FgThread.getHandler(); + mUserInfoStore = new UserInfoStore(mContext); mSettingsStore = new LocationSettingsStore(mContext, mHandler); mLocationUsageLogger = new LocationUsageLogger(); - mCurrentUserId = UserHandle.USER_NULL; - mCurrentUserProfiles = new int[]{UserHandle.USER_NULL}; - // set up passive provider - we do this early because it has no dependencies on system // services or external code that isn't ready yet, and because this allows the variable to // be final. other more complex providers are initialized later, when system services are @@ -277,6 +270,7 @@ public class LocationManagerService extends ILocationManager.Stub { } private void onSystemReady() { + mUserInfoStore.onSystemReady(); mSettingsStore.onSystemReady(); synchronized (mLock) { @@ -284,7 +278,6 @@ public class LocationManagerService extends ILocationManager.Stub { mAppOps = mContext.getSystemService(AppOpsManager.class); mPowerManager = mContext.getSystemService(PowerManager.class); mActivityManager = mContext.getSystemService(ActivityManager.class); - mUserManager = mContext.getSystemService(UserManager.class); mLocationFudger = new LocationFudger(mContext, mHandler); mGeofenceManager = new GeofenceManager(mContext, mSettingsStore); @@ -372,10 +365,13 @@ public class LocationManagerService extends ILocationManager.Stub { } }.register(mContext, mHandler.getLooper(), true); + mUserInfoStore.addListener((oldUserId, newUserId) -> { + synchronized (mLock) { + onUserChangedLocked(oldUserId, newUserId); + } + }); + IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_SWITCHED); - intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_SCREEN_ON); @@ -388,14 +384,6 @@ public class LocationManagerService extends ILocationManager.Stub { } synchronized (mLock) { switch (action) { - case Intent.ACTION_USER_SWITCHED: - onUserChangedLocked( - intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - break; - case Intent.ACTION_MANAGED_PROFILE_ADDED: - case Intent.ACTION_MANAGED_PROFILE_REMOVED: - onUserProfilesChangedLocked(); - break; case Intent.ACTION_SCREEN_ON: case Intent.ACTION_SCREEN_OFF: onScreenStateChangedLocked(); @@ -405,11 +393,10 @@ public class LocationManagerService extends ILocationManager.Stub { } }, UserHandle.ALL, intentFilter, null, mHandler); - // switching the user from null to system here performs the bulk of the initialization + // switching the user from null to current here performs the bulk of the initialization // work. the user being changed will cause a reload of all user specific settings, which // causes initialization, and propagates changes until a steady state is reached - mCurrentUserId = UserHandle.USER_NULL; - onUserChangedLocked(ActivityManager.getCurrentUser()); + onUserChangedLocked(UserHandle.USER_NULL, mUserInfoStore.getCurrentUserId()); } } @@ -551,16 +538,6 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void onUserProfilesChangedLocked() { - mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId); - } - - @GuardedBy("mLock") - private boolean isCurrentProfileLocked(int userId) { - return ArrayUtils.contains(mCurrentUserProfiles, userId); - } - - @GuardedBy("mLock") private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) { PackageManager pm = mContext.getPackageManager(); String systemPackageName = mContext.getPackageName(); @@ -568,7 +545,7 @@ public class LocationManagerService extends ILocationManager.Stub { List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser( new Intent(FUSED_LOCATION_SERVICE_ACTION), - PackageManager.GET_META_DATA, mCurrentUserId); + PackageManager.GET_META_DATA, mUserInfoStore.getCurrentUserId()); for (ResolveInfo rInfo : rInfos) { String packageName = rInfo.serviceInfo.packageName; @@ -752,27 +729,18 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void onUserChangedLocked(int userId) { - if (mCurrentUserId == userId) { - return; - } - + private void onUserChangedLocked(int oldUserId, int newUserId) { if (D) { - Log.d(TAG, "foreground user is changing to " + userId); + Log.d(TAG, "foreground user is changing to " + newUserId); } - int oldUserId = mCurrentUserId; - mCurrentUserId = userId; - onUserProfilesChangedLocked(); - - // let providers know the current user has changed for (LocationProviderManager manager : mProviderManagers) { // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility mSettingsStore.setLocationProviderAllowed(manager.getName(), - manager.isUseable(mCurrentUserId), mCurrentUserId); + manager.isUseable(newUserId), newUserId); manager.onUseableChangedLocked(oldUserId); - manager.onUseableChangedLocked(mCurrentUserId); + manager.onUseableChangedLocked(newUserId); } } @@ -786,8 +754,12 @@ public class LocationManagerService extends ILocationManager.Stub { // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary protected final MockableLocationProvider mProvider; + // useable state for parent user ids, no entry implies false. location state is only kept + // for parent user ids, the location state for a profile user id is assumed to be the same + // as for the parent. if querying this structure, ensure that the user id being used is a + // parent id or the results may be incorrect. @GuardedBy("mLock") - private final SparseArray<Boolean> mUseable; // combined state for each user id + private final SparseArray<Boolean> mUseable; private LocationProviderManager(String name) { mName = name; @@ -795,6 +767,9 @@ public class LocationManagerService extends ILocationManager.Stub { // initialize last since this lets our reference escape mProvider = new MockableLocationProvider(mContext, mLock, this); + + // we can assume all users start with unuseable location state since the initial state + // of all providers is disabled. no need to initialize mUseable further. } public String getName() { @@ -868,29 +843,6 @@ public class LocationManagerService extends ILocationManager.Stub { mProvider.sendExtraCommand(uid, pid, command, extras); } - public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { - synchronized (mLock) { - pw.print(mName + " provider"); - if (mProvider.isMock()) { - pw.print(" [mock]"); - } - pw.println(":"); - - pw.increaseIndent(); - - pw.println("useable=" + isUseable(mCurrentUserId)); - if (!isUseable(mCurrentUserId)) { - pw.println("enabled=" + mProvider.getState().enabled); - } - - pw.println("properties=" + mProvider.getState().properties); - } - - mProvider.dump(fd, pw, args); - - pw.decreaseIndent(); - } - @GuardedBy("mLock") @Override public void onReportLocation(Location location) { @@ -927,18 +879,20 @@ public class LocationManagerService extends ILocationManager.Stub { // it would be more correct to call this for all users, but we know this can // only affect the current user since providers are disabled for non-current // users - onUseableChangedLocked(mCurrentUserId); + onUseableChangedLocked(mUserInfoStore.getCurrentUserId()); } } - @GuardedBy("mLock") public boolean isUseable() { - return isUseable(mCurrentUserId); + return isUseable(mUserInfoStore.getCurrentUserId()); } - @GuardedBy("mLock") public boolean isUseable(int userId) { synchronized (mLock) { + // normalize user id to always refer to parent since profile state is always the + // same as parent state + userId = mUserInfoStore.getParentUserId(userId); + return mUseable.get(userId, Boolean.FALSE); } } @@ -950,15 +904,20 @@ public class LocationManagerService extends ILocationManager.Stub { return; } + // normalize user id to always refer to parent since profile state is always the same + // as parent state + userId = mUserInfoStore.getParentUserId(userId); + // if any property that contributes to "useability" here changes state, it MUST result // in a direct or indrect call to onUseableChangedLocked. this allows the provider to // guarantee that it will always eventually reach the correct state. - boolean useable = isCurrentProfileLocked(userId) + boolean useable = (userId == mUserInfoStore.getCurrentUserId()) && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled; if (useable == isUseable(userId)) { return; } + mUseable.put(userId, useable); if (D) { @@ -986,6 +945,30 @@ public class LocationManagerService extends ILocationManager.Stub { updateProviderUseableLocked(this); } + + public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { + synchronized (mLock) { + pw.print(mName + " provider"); + if (mProvider.isMock()) { + pw.print(" [mock]"); + } + pw.println(":"); + + pw.increaseIndent(); + + boolean useable = isUseable(); + pw.println("useable=" + useable); + if (!useable) { + pw.println("enabled=" + mProvider.getState().enabled); + } + + pw.println("properties=" + mProvider.getState().properties); + } + + mProvider.dump(fd, pw, args); + + pw.decreaseIndent(); + } } class PassiveLocationProviderManager extends LocationProviderManager { @@ -1626,7 +1609,7 @@ public class LocationManagerService extends ILocationManager.Stub { ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); if (records != null) { for (UpdateRecord record : records) { - if (!isCurrentProfileLocked( + if (!mUserInfoStore.isCurrentUserOrProfile( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { continue; } @@ -1691,7 +1674,7 @@ public class LocationManagerService extends ILocationManager.Stub { // initialize the low power mode to true and set to false if any of the records requires providerRequest.setLowPowerMode(true); for (UpdateRecord record : records) { - if (!isCurrentProfileLocked( + if (!mUserInfoStore.isCurrentUserOrProfile( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { continue; } @@ -1750,7 +1733,7 @@ public class LocationManagerService extends ILocationManager.Stub { // TODO: overflow long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2; for (UpdateRecord record : records) { - if (isCurrentProfileLocked( + if (mUserInfoStore.isCurrentUserOrProfile( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { LocationRequest locationRequest = record.mRequest; @@ -2243,8 +2226,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (manager == null) return null; // only the current user or location providers may get location this way - if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage( - packageName)) { + if (!mUserInfoStore.isCurrentUserOrProfile(UserHandle.getUserId(uid)) + && !isProviderPackage(packageName)) { return null; } @@ -2773,12 +2756,11 @@ public class LocationManagerService extends ILocationManager.Stub { } int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid); - if (!isCurrentProfileLocked(receiverUserId) + if (!mUserInfoStore.isCurrentUserOrProfile(receiverUserId) && !isProviderPackage(receiver.mCallerIdentity.mPackageName)) { if (D) { Log.d(TAG, "skipping loc update for background user " + receiverUserId + - " (current user: " + mCurrentUserId + ", app: " + - receiver.mCallerIdentity.mPackageName + ")"); + " (app: " + receiver.mCallerIdentity.mPackageName + ")"); } continue; } @@ -3028,9 +3010,17 @@ public class LocationManagerService extends ILocationManager.Stub { + TimeUtils.logTimeOfDay(System.currentTimeMillis())); ipw.println(", Current Elapsed Time: " + TimeUtils.formatDuration(SystemClock.elapsedRealtime())); - ipw.println("Current user: " + mCurrentUserId + " " + Arrays.toString( - mCurrentUserProfiles)); - ipw.println("Location Mode: " + isLocationEnabledForUser(mCurrentUserId)); + + ipw.println("User Info:"); + ipw.increaseIndent(); + mUserInfoStore.dump(fd, ipw, args); + ipw.decreaseIndent(); + + ipw.println("Location Settings:"); + ipw.increaseIndent(); + mSettingsStore.dump(fd, ipw, args); + ipw.decreaseIndent(); + ipw.println("Battery Saver Location Mode: " + locationPowerSaveModeToString(mBatterySaverMode)); @@ -3096,21 +3086,14 @@ public class LocationManagerService extends ILocationManager.Stub { mLocationFudger.dump(fd, ipw, args); ipw.decreaseIndent(); } - } - - ipw.println("Location Settings:"); - ipw.increaseIndent(); - mSettingsStore.dump(fd, ipw, args); - ipw.decreaseIndent(); - ipw.println("Location Providers:"); - ipw.increaseIndent(); - for (LocationProviderManager manager : mProviderManagers) { - manager.dump(fd, ipw, args); - } - ipw.decreaseIndent(); + ipw.println("Location Providers:"); + ipw.increaseIndent(); + for (LocationProviderManager manager : mProviderManagers) { + manager.dump(fd, ipw, args); + } + ipw.decreaseIndent(); - synchronized (mLock) { if (mGnssManagerService != null) { ipw.println("GNSS:"); ipw.increaseIndent(); diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index b26ef92557e1..d1d1cb3566d2 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -523,7 +523,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public void accept(INetworkScoreCache networkScoreCache, Object cookie) { - int filterType = NetworkScoreManager.CACHE_FILTER_NONE; + int filterType = NetworkScoreManager.SCORE_FILTER_NONE; if (cookie instanceof Integer) { filterType = (Integer) cookie; } @@ -547,17 +547,17 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList, int filterType) { switch (filterType) { - case NetworkScoreManager.CACHE_FILTER_NONE: + case NetworkScoreManager.SCORE_FILTER_NONE: return scoredNetworkList; - case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK: + case NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK: if (mCurrentNetworkFilter == null) { mCurrentNetworkFilter = new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext)); } return mCurrentNetworkFilter.apply(scoredNetworkList); - case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS: + case NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS: if (mScanResultsFilter == null) { mScanResultsFilter = new ScanResultsScoreCacheFilter( new ScanResultsSupplier(mContext)); diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java index d20936c2d217..7894788cb0b6 100644 --- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java +++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java @@ -154,17 +154,20 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU private void onPollNetworkTimeUnderWakeLock(int event) { // Force an NTP fix when outdated - if (mTime.getCacheAge() >= mPollingIntervalMs) { + NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult(); + if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) { if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh"); mTime.forceRefresh(); + cachedNtpResult = mTime.getCachedTimeResult(); } - if (mTime.getCacheAge() < mPollingIntervalMs) { + if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) { // Obtained fresh fix; schedule next normal update resetAlarm(mPollingIntervalMs); // Suggest the time to the time detector. It may choose use it to set the system clock. - TimestampedValue<Long> timeSignal = mTime.getCachedNtpTimeSignal(); + TimestampedValue<Long> timeSignal = new TimestampedValue<>( + cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis()); NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal); timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event); mTimeDetector.suggestNetworkTime(timeSuggestion); @@ -275,8 +278,11 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU TimeUtils.formatDuration(mPollingIntervalShorterMs, pw); pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax); pw.println("\nTryAgainCounter: " + mTryAgainCounter); - pw.println("NTP cache age: " + mTime.getCacheAge()); - pw.println("NTP cache certainty: " + mTime.getCacheCertainty()); + NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult(); + pw.println("NTP cache result: " + ntpResult); + if (ntpResult != null) { + pw.println("NTP result age: " + ntpResult.getAgeMillis()); + } pw.println(); } } diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java index 190fff1f669c..21fa9f9a9401 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java @@ -46,4 +46,7 @@ public interface PersistentDataBlockManagerInternal { /** Update the OEM unlock enabled bit, bypassing user restriction checks. */ void forceOemUnlockEnabled(boolean enabled); + + /** Retrieves the UID that can access the persistent data partition. */ + int getAllowedUid(); } diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 73c852083cfd..00d8b0f1bed4 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -680,6 +680,11 @@ public class PersistentDataBlockService extends SystemService { writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size)); } + @Override + public int getAllowedUid() { + return mAllowedUid; + } + private void writeInternal(byte[] data, long offset, int dataLength) { checkArgument(data == null || data.length > 0, "data must be null or non-empty"); checkArgument( diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index c5409f85bde3..f46b9ae3e8fb 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -18,18 +18,23 @@ package com.android.server; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityThread; import android.content.Context; import android.content.pm.UserInfo; import android.os.IBinder; import android.os.ServiceManager; +import android.os.UserHandle; import android.os.UserManager; import com.android.server.pm.UserManagerService; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -57,15 +62,17 @@ import java.util.List; * * {@hide} */ +//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) public abstract class SystemService { + /** @hide */ // TODO(b/133242016) STOPSHIP: change to false before R ships protected static final boolean DEBUG_USER = true; /* - * Boot Phases + * The earliest boot phase the system send to system services on boot. */ - public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency? + public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; /** * After receiving this boot phase, services can obtain lock settings data. @@ -98,13 +105,70 @@ public abstract class SystemService { * After receiving this boot phase, services can allow user interaction with the device. * This phase occurs when boot has completed and the home application has started. * System services may prefer to listen to this phase rather than registering a - * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency. + * broadcast receiver for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED} + * to reduce overall latency. */ public static final int PHASE_BOOT_COMPLETED = 1000; + /** @hide */ + @IntDef(flag = true, prefix = { "PHASE_" }, value = { + PHASE_WAIT_FOR_DEFAULT_DISPLAY, + PHASE_LOCK_SETTINGS_READY, + PHASE_SYSTEM_SERVICES_READY, + PHASE_DEVICE_SPECIFIC_SERVICES_READY, + PHASE_ACTIVITY_MANAGER_READY, + PHASE_THIRD_PARTY_APPS_CAN_START, + PHASE_BOOT_COMPLETED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BootPhase {} + private final Context mContext; /** + * Class representing user in question in the lifecycle callbacks. + * @hide + */ + //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) + public static final class TargetUser { + @NonNull + private final UserInfo mUserInfo; + + /** @hide */ + public TargetUser(@NonNull UserInfo userInfo) { + mUserInfo = userInfo; + } + + /** + * @return The information about the user. <b>NOTE: </b> this is a "live" object + * referenced by {@link UserManagerService} and hence should not be modified. + * + * @hide + */ + @NonNull + public UserInfo getUserInfo() { + return mUserInfo; + } + + /** + * @return the target {@link UserHandle}. + */ + @NonNull + public UserHandle getUserHandle() { + return mUserInfo.getUserHandle(); + } + + /** + * @return the integer user id + * + * @hide + */ + public int getUserIdentifier() { + return mUserInfo.id; + } + } + + /** * Initializes the system service. * <p> * Subclasses must define a single argument constructor that accepts the context @@ -113,13 +177,14 @@ public abstract class SystemService { * * @param context The system server context. */ - public SystemService(Context context) { + public SystemService(@NonNull Context context) { mContext = context; } /** * Gets the system context. */ + @NonNull public final Context getContext() { return mContext; } @@ -128,6 +193,8 @@ public abstract class SystemService { * Get the system UI context. This context is to be used for displaying UI. It is themable, * which means resources can be overridden at runtime. Do not use to retrieve properties that * configure the behavior of the device that is not UX related. + * + * @hide */ public final Context getUiContext() { // This has already been set up by the time any SystemServices are created. @@ -137,15 +204,16 @@ public abstract class SystemService { /** * Returns true if the system is running in safe mode. * TODO: we should define in which phase this becomes valid + * + * @hide */ public final boolean isSafeMode() { return getManager().isSafeMode(); } /** - * Called when the dependencies listed in the @Service class-annotation are available - * and after the chosen start phase. - * When this method returns, the service should be published. + * Called when the system service should publish a binder service using + * {@link #publishBinderService(String, IBinder).} */ public abstract void onStart(); @@ -155,7 +223,7 @@ public abstract class SystemService { * * @param phase The current boot phase. */ - public void onBootPhase(int phase) {} + public void onBootPhase(@BootPhase int phase) {} /** * Checks if the service should be available for the given user. @@ -163,12 +231,14 @@ public abstract class SystemService { * <p>By default returns {@code true}, but subclasses should extend for optimization, if they * don't support some types (like headless system user). */ - public boolean isSupported(@NonNull UserInfo userInfo) { + public boolean isSupportedUser(@NonNull TargetUser user) { return true; } /** - * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}. + * Helper method used to dump which users are {@link #onStartUser(TargetUser) supported}. + * + * @hide */ protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) { final List<UserInfo> allUsers = UserManager.get(mContext).getUsers(); @@ -187,34 +257,59 @@ public abstract class SystemService { } /** - * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default - * calls this method). + * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide */ @Deprecated public void onStartUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide + */ + @Deprecated + public void onStartUser(@NonNull UserInfo userInfo) { + onStartUser(userInfo.id); + } + + /** * Called when a new user is starting, for system services to initialize any per-user * state they maintain for running users. * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onStartUser(@NonNull UserInfo userInfo) { - onStartUser(userInfo.id); + public void onStartUser(@NonNull TargetUser user) { + onStartUser(user.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by + * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by * default calls this method). + * + * @hide */ @Deprecated public void onUnlockUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by + * default calls this method). + * + * @hide + */ + @Deprecated + public void onUnlockUser(@NonNull UserInfo userInfo) { + onUnlockUser(userInfo.id); + } + + /** * Called when an existing user is in the process of being unlocked. This * means the credential-encrypted storage for that user is now available, * and encryption-aware component filtering is no longer in effect. @@ -226,90 +321,127 @@ public abstract class SystemService { * {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of * these states. * <p> - * This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onUnlockUser(@NonNull UserInfo userInfo) { - onUnlockUser(userInfo.id); + public void onUnlockUser(@NonNull TargetUser user) { + onUnlockUser(user.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead + * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead * (which by default calls this method). + * + * @hide */ @Deprecated - public void onSwitchUser(@UserIdInt int userId) {} + public void onSwitchUser(@UserIdInt int toUserId) {} + + /** + * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead + * (which by default calls this method). + * + * @hide + */ + @Deprecated + public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) { + onSwitchUser(to.id); + } /** * Called when switching to a different foreground user, for system services that have * special behavior for whichever user is currently in the foreground. This is called * before any application processes are aware of the new user. * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either - * of the users ({@code from} or {@code to}). + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * either of the users ({@code from} or {@code to}). * * <b>NOTE: </b> both {@code from} and {@code to} are "live" objects * referenced by {@link UserManagerService} and hence should not be modified. * - * @param from The information about the user being switched from. - * @param to The information about the user being switched from to. + * @param from the user switching from + * @param to the user switching to */ - public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) { - onSwitchUser(to.id); + public void onSwitchUser(@Nullable TargetUser from, @NonNull TargetUser to) { + onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default - * calls this method). + * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide */ @Deprecated public void onStopUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide + */ + @Deprecated + public void onStopUser(@NonNull UserInfo user) { + onStopUser(user.id); + + } + + /** * Called when an existing user is stopping, for system services to finalize any per-user * state they maintain for running users. This is called prior to sending the SHUTDOWN * broadcast to the user; it is a good place to stop making use of any resources of that * user (such as binding to a service running in the user). * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * * <p>NOTE: This is the last callback where the callee may access the target user's CE storage. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onStopUser(@NonNull UserInfo userInfo) { - onStopUser(userInfo.id); + public void onStopUser(@NonNull TargetUser user) { + onStopUser(user.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by + * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by * default calls this method). + * + * @hide */ @Deprecated public void onCleanupUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by + * default calls this method). + * + * @hide + */ + @Deprecated + public void onCleanupUser(@NonNull UserInfo user) { + onCleanupUser(user.id); + } + + /** * Called when an existing user is stopping, for system services to finalize any per-user * state they maintain for running users. This is called after all application process * teardown of the user is complete. * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * * <p>NOTE: When this callback is called, the CE storage for the target user may not be - * accessible already. Use {@link #onStopUser(UserInfo)} instead if you need to access the CE + * accessible already. Use {@link #onStopUser(TargetUser)} instead if you need to access the CE * storage. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onCleanupUser(@NonNull UserInfo userInfo) { - onCleanupUser(userInfo.id); + public void onCleanupUser(@NonNull TargetUser user) { + onCleanupUser(user.getUserInfo()); } /** @@ -318,7 +450,7 @@ public abstract class SystemService { * @param name the name of the new service * @param service the service object */ - protected final void publishBinderService(String name, IBinder service) { + protected final void publishBinderService(@NonNull String name, @NonNull IBinder service) { publishBinderService(name, service, false); } @@ -330,7 +462,7 @@ public abstract class SystemService { * @param allowIsolated set to true to allow isolated sandboxed processes * to access this service */ - protected final void publishBinderService(String name, IBinder service, + protected final void publishBinderService(@NonNull String name, @NonNull IBinder service, boolean allowIsolated) { publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT); } @@ -343,6 +475,8 @@ public abstract class SystemService { * @param allowIsolated set to true to allow isolated sandboxed processes * to access this service * @param dumpPriority supported dump priority levels as a bitmask + * + * @hide */ protected final void publishBinderService(String name, IBinder service, boolean allowIsolated, int dumpPriority) { @@ -351,6 +485,8 @@ public abstract class SystemService { /** * Get a binder service by its name. + * + * @hide */ protected final IBinder getBinderService(String name) { return ServiceManager.getService(name); @@ -358,6 +494,8 @@ public abstract class SystemService { /** * Publish the service so it is only accessible to the system process. + * + * @hide */ protected final <T> void publishLocalService(Class<T> type, T service) { LocalServices.addService(type, service); @@ -365,6 +503,8 @@ public abstract class SystemService { /** * Get a local service by interface. + * + * @hide */ protected final <T> T getLocalService(Class<T> type) { return LocalServices.getService(type); diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index c7157981ada2..c0f43a81eca4 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.os.UserManagerInternal; import android.util.Slog; +import com.android.server.SystemService.TargetUser; import com.android.server.utils.TimingsTraceAndSlog; import java.io.File; @@ -264,26 +265,26 @@ public class SystemServiceManager { @UserIdInt int curUserId, @UserIdInt int prevUserId) { t.traceBegin("ssm." + onWhat + "User-" + curUserId); Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId); - final UserInfo curUserInfo = getUserInfo(curUserId); - final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null - : getUserInfo(prevUserId); + final TargetUser curUser = new TargetUser(getUserInfo(curUserId)); + final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null + : new TargetUser(getUserInfo(prevUserId)); final int serviceLen = mServices.size(); for (int i = 0; i < serviceLen; i++) { final SystemService service = mServices.get(i); final String serviceName = service.getClass().getName(); - boolean supported = service.isSupported(curUserInfo); + boolean supported = service.isSupportedUser(curUser); // Must check if either curUser or prevUser is supported (for example, if switching from // unsupported to supported, we still need to notify the services) - if (!supported && prevUserInfo != null) { - supported = service.isSupported(prevUserInfo); + if (!supported && prevUser != null) { + supported = service.isSupportedUser(prevUser); } if (!supported) { if (DEBUG) { Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service " + serviceName + " because it's not supported (curUser: " - + curUserInfo + ", prevUser:" + prevUserInfo + ")"); + + curUser + ", prevUser:" + prevUser + ")"); } else { Slog.i(TAG, "Skipping " + onWhat + "User-" + curUserId + " on " + serviceName); @@ -295,25 +296,25 @@ public class SystemServiceManager { try { switch (onWhat) { case SWITCH: - service.onSwitchUser(prevUserInfo, curUserInfo); + service.onSwitchUser(prevUser, curUser); break; case START: - service.onStartUser(curUserInfo); + service.onStartUser(curUser); break; case UNLOCK: - service.onUnlockUser(curUserInfo); + service.onUnlockUser(curUser); break; case STOP: - service.onStopUser(curUserInfo); + service.onStopUser(curUser); break; case CLEANUP: - service.onCleanupUser(curUserInfo); + service.onCleanupUser(curUser); break; default: throw new IllegalArgumentException(onWhat + " what?"); } } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo + Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser + " to service " + serviceName, ex); } warnIfTooLong(SystemClock.elapsedRealtime() - time, service, diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 3e6ccb572860..2b7745b7428a 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -83,7 +83,6 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.ITelephonyRegistry; -import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyPermissions; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; @@ -2194,6 +2193,17 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private static final String PHONE_CONSTANTS_STATE_KEY = "state"; private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription"; + /** + * Broadcast Action: The phone's signal strength has changed. The intent will have the + * following extra values: + * phoneName - A string version of the phone name. + * asu - A numeric value for the signal strength. + * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu). + * The following special values are defined: + * 0 means "-113 dBm or less".31 means "-51 dBm or greater". + */ + public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR"; + private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) { long ident = Binder.clearCallingIdentity(); try { @@ -2228,7 +2238,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { Binder.restoreCallingIdentity(ident); } - Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED); + Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED); Bundle data = new Bundle(); fillInSignalStrengthNotifierBundle(signalStrength, data); intent.putExtras(data); diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index c27b0da780b7..ed3bab97ca19 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -218,7 +218,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { // Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up // resources, even for binder death or unwanted calls. synchronized (mTestNetworkTracker) { - mTestNetworkTracker.remove(netId); + mTestNetworkTracker.remove(network.netId); } } } @@ -337,7 +337,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { callingUid, binder); - mTestNetworkTracker.put(agent.netId, agent); + mTestNetworkTracker.put(agent.network.netId, agent); } } catch (SocketException e) { throw new UncheckedIOException(e); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index a372fca07728..debc2a116934 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -2129,16 +2129,6 @@ public class AccountManagerService } @Override - public void removeAccount(IAccountManagerResponse response, Account account, - boolean expectActivityLaunch) { - removeAccountAsUser( - response, - account, - expectActivityLaunch, - UserHandle.getCallingUserId()); - } - - @Override public void removeAccountAsUser(IAccountManagerResponse response, Account account, boolean expectActivityLaunch, int userId) { final int callingUid = Binder.getCallingUid(); @@ -4454,12 +4444,6 @@ public class AccountManagerService @Override @NonNull - public Account[] getAccounts(String type, String opPackageName) { - return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName); - } - - @Override - @NonNull public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) { int callingUid = Binder.getCallingUid(); if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index a58bd9bf32ff..e2a036aec172 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2068,7 +2068,7 @@ public final class ActiveServices { + " type=" + resolvedType + " callingUid=" + callingUid); userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false, - ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", + ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service", callingPackage); ServiceMap smap = getServiceMapLocked(userId); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c21adb08270e..8f6d981064de 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16408,6 +16408,22 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean updateMccMncConfiguration(String mcc, String mnc) { + int mccInt, mncInt; + try { + mccInt = Integer.parseInt(mcc); + mncInt = Integer.parseInt(mnc); + } catch (NumberFormatException | StringIndexOutOfBoundsException ex) { + Slog.e(TAG, "Error parsing mcc: " + mcc + " mnc: " + mnc + ". ex=" + ex); + return false; + } + Configuration config = new Configuration(); + config.mcc = mccInt; + config.mnc = mncInt == 0 ? Configuration.MNC_ZERO : mncInt; + return mActivityTaskManager.updateConfiguration(config); + } + + @Override public int getLaunchedFromUid(IBinder activityToken) { return mActivityTaskManager.getLaunchedFromUid(activityToken); } diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java index ebfc2a011e88..549051df65ea 100644 --- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java @@ -32,6 +32,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.UserManager; import android.view.LayoutInflater; import android.view.View; @@ -81,8 +82,15 @@ final class CarUserSwitchingDialog extends UserSwitchingDialog { .setImageDrawable(drawable); } - ((TextView) view.findViewById(R.id.user_loading)) - .setText(res.getString(R.string.car_loading_profile)); + TextView msgView = view.findViewById(R.id.user_loading); + // TODO: use developer settings instead + if (Build.IS_DEBUGGABLE) { + // TODO: use specific string + msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from " + + mOldUser.id + " to " + mNewUser.id + ")"); + } else { + msgView.setText(res.getString(R.string.car_loading_profile)); + } setView(view); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b9f2e76c6ecb..e11008c246dd 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1562,8 +1562,7 @@ public final class ProcessList { throw e.rethrowAsRuntimeException(); } int numGids = 3; - if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER - || mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { + if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { numGids++; } /* diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index eb1ab3863624..8ae18ff68b66 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -16,12 +16,14 @@ package com.android.server.am; +import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; import static android.app.ActivityManager.USER_OP_SUCCESS; +import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; @@ -1641,11 +1643,11 @@ class UserController implements Handler.Callback { if (callingUid != 0 && callingUid != SYSTEM_UID) { final boolean allow; + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId); if (mInjector.isCallerRecents(callingUid) - && callingUserId == getCurrentUserId() && isSameProfileGroup(callingUserId, targetUserId)) { - // If the caller is Recents and it is running in the current user, we then allow it - // to access its profiles. + // If the caller is Recents and the caller has ownership of the profile group, + // we then allow it to access its profiles. allow = true; } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid, callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { @@ -1654,6 +1656,9 @@ class UserController implements Handler.Callback { } else if (allowMode == ALLOW_FULL_ONLY) { // We require full access, sucks to be you. allow = false; + } else if (canInteractWithAcrossProfilesPermission( + allowMode, isSameProfileGroup, callingPid, callingUid)) { + allow = true; } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) { // If the caller does not have either permission, they are always doomed. @@ -1661,10 +1666,11 @@ class UserController implements Handler.Callback { } else if (allowMode == ALLOW_NON_FULL) { // We are blanket allowing non-full access, you lucky caller! allow = true; - } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) { + } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE + || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { // We may or may not allow this depending on whether the two users are // in the same profile. - allow = isSameProfileGroup(callingUserId, targetUserId); + allow = isSameProfileGroup; } else { throw new IllegalArgumentException("Unknown mode: " + allowMode); } @@ -1690,6 +1696,11 @@ class UserController implements Handler.Callback { if (allowMode != ALLOW_FULL_ONLY) { builder.append(" or "); builder.append(INTERACT_ACROSS_USERS); + if (isSameProfileGroup + && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + builder.append(" or "); + builder.append(INTERACT_ACROSS_PROFILES); + } } String msg = builder.toString(); Slog.w(TAG, msg); @@ -1710,6 +1721,19 @@ class UserController implements Handler.Callback { return targetUserId; } + private boolean canInteractWithAcrossProfilesPermission( + int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) { + if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + return false; + } + if (!isSameProfileGroup) { + return false; + } + return mInjector.checkComponentPermission( + INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1, + /*exported= */true) == PackageManager.PERMISSION_GRANTED; + } + int unsafeConvertIncomingUser(@UserIdInt int userId) { return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) ? getCurrentUserId(): userId; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a7593c7a43ec..e3b761e0773d 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1759,8 +1759,9 @@ public class AppOpsService extends IAppOpsService.Stub { ? opNames.toArray(new String[opNames.size()]) : null; // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter, - beginTimeMillis, endTimeMillis, flags, callback); + mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps, + mHistoricalRegistry, uid, packageName, featureId, opNamesArray, filter, + beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @Override @@ -1778,8 +1779,9 @@ public class AppOpsService extends IAppOpsService.Stub { ? opNames.toArray(new String[opNames.size()]) : null; // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray, - filter, beginTimeMillis, endTimeMillis, flags, callback); + mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw, + mHistoricalRegistry, uid, packageName, featureId, opNamesArray, + filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @Override diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index bc4dc8f4496c..cd2272aa421c 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1700,6 +1700,14 @@ public class AudioService extends IAudioService.Stub } } + /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */ + public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes( + @NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes); + enforceModifyAudioRoutingPermission(); + return AudioSystem.getDevicesForAttributes(attributes); + } + /** @see AudioManager#adjustVolume(int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index f15d999e1006..8d261762a19b 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -367,6 +367,8 @@ final class CompatConfig { CompatConfig config = new CompatConfig(androidBuildClassifier, context); config.initConfigFromLib(Environment.buildPath( Environment.getRootDirectory(), "etc", "compatconfig")); + config.initConfigFromLib(Environment.buildPath( + Environment.getRootDirectory(), "system_ext", "etc", "compatconfig")); return config; } diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index aea6d8d24312..f636d67c3d1d 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -116,7 +116,8 @@ public class Nat464Xlat extends BaseNetworkObserver { && !lp.hasIpv4Address(); // If the network tells us it doesn't use clat, respect that. - final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat; + final boolean skip464xlat = (nai.netAgentConfig() != null) + && nai.netAgentConfig().skip464xlat; return supported && connected && isIpv6OnlyNetwork && !skip464xlat; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 5e085ca293a4..c1ab55106ab1 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -23,9 +23,9 @@ import android.net.INetd; import android.net.INetworkMonitor; import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.net.NetworkMisc; import android.net.NetworkMonitorManager; import android.net.NetworkRequest; import android.net.NetworkScore; @@ -127,7 +127,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // This should only be modified by ConnectivityService, via setNetworkCapabilities(). // TODO: make this private with a getter. public NetworkCapabilities networkCapabilities; - public final NetworkMisc networkMisc; + public final NetworkAgentConfig networkAgentConfig; // Indicates if netd has been told to create this Network. From this point on the appropriate // routing rules are setup and routes are added so packets can begin flowing over the Network. // This is a sticky bit; once set it is never cleared. @@ -261,7 +261,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context, - Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd, + Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) { this.messenger = messenger; asyncChannel = ac; @@ -274,7 +274,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mConnService = connService; mContext = context; mHandler = handler; - networkMisc = misc; + networkAgentConfig = config; this.factorySerialNumber = factorySerialNumber; } @@ -309,8 +309,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { return mConnService; } - public NetworkMisc netMisc() { - return networkMisc; + public NetworkAgentConfig netAgentConfig() { + return networkAgentConfig; } public Handler handler() { @@ -487,7 +487,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // selected and we're trying to see what its score could be. This ensures that we don't tear // down an explicitly selected network before the user gets a chance to prefer it when // a higher-scoring network (e.g., Ethernet) is available. - if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) { + if (networkAgentConfig.explicitlySelected + && (networkAgentConfig.acceptUnvalidated || pretendValidated)) { return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE; } @@ -533,7 +534,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { 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 = (networkMisc != null) ? networkMisc.subscriberId : null; + final String subscriberId = (networkAgentConfig != null) + ? networkAgentConfig.subscriberId : null; return new NetworkState(new NetworkInfo(networkInfo), new LinkProperties(linkProperties), new NetworkCapabilities(networkCapabilities), network, subscriberId, null); @@ -641,13 +643,13 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + "created{" + created + "} lingering{" + isLingering() + "} " - + "explicitlySelected{" + networkMisc.explicitlySelected + "} " - + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} " + + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} " + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + "captivePortalValidationPending{" + captivePortalValidationPending + "} " + "partialConnectivity{" + partialConnectivity + "} " - + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} " + + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} " + "clat{" + clatd + "} " + "}"; } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 6b70e5f00330..1d2a9059c453 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -53,11 +53,11 @@ import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; -import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkMisc; +import android.net.NetworkProvider; import android.net.RouteInfo; import android.net.UidRange; import android.net.VpnService; @@ -848,7 +848,7 @@ public class Vpn { } public int getNetId() { - return mNetworkAgent != null ? mNetworkAgent.netId : NETID_UNSET; + return mNetworkAgent != null ? mNetworkAgent.network.netId : NETID_UNSET; } private LinkProperties makeLinkProperties() { @@ -914,7 +914,7 @@ public class Vpn { * has certain changes, in which case this method would just return {@code false}. */ private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) { - // NetworkMisc cannot be updated without registering a new NetworkAgent. + // NetworkAgentConfig cannot be updated without registering a new NetworkAgent. if (oldConfig.allowBypass != mConfig.allowBypass) { Log.i(TAG, "Handover not possible due to changes to allowBypass"); return false; @@ -947,8 +947,8 @@ public class Vpn { mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null); - NetworkMisc networkMisc = new NetworkMisc(); - networkMisc.allowBypass = mConfig.allowBypass && !mLockdown; + NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); + networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown; mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid()); mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle, @@ -957,8 +957,8 @@ public class Vpn { try { mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */, mNetworkInfo, mNetworkCapabilities, lp, - ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc, - NetworkFactory.SerialNumber.VPN) { + ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, + NetworkProvider.ID_VPN) { @Override public void unwanted() { // We are user controlled, not driven by NetworkRequest. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d7ae5b5c91bd..e39d6d5389e3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1305,21 +1305,13 @@ public final class DisplayManagerService extends SystemService { } private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) { - synchronized (mSyncRoot) { - final IBinder token = getDisplayToken(displayId); - if (token == null) { - return null; - } - final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId); - if (logicalDisplay == null) { - return null; - } - - final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); - return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), - displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), - false /* useIdentityTransform */, 0 /* rotation */); + final IBinder token = getDisplayToken(displayId); + if (token == null) { + return null; } + return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe( + token, new Rect(), 0 /* width */, 0 /* height */, + false /* useIdentityTransform */, 0 /* rotation */); } @VisibleForTesting diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java index c58000fa6cee..8206fef90ab7 100644 --- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java @@ -41,7 +41,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AbstractRemoteService; import com.android.internal.os.BackgroundThread; -import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -713,8 +712,8 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem } /** - * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)} - * check). + * Gets a list of all supported users (i.e., those that pass the + * {@link #isSupportedUser(TargetUser)}check). */ @NonNull protected List<UserInfo> getSupportedUsers() { @@ -723,7 +722,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem final List<UserInfo> supportedUsers = new ArrayList<>(size); for (int i = 0; i < size; i++) { final UserInfo userInfo = allUsers[i]; - if (isSupported(userInfo)) { + if (isSupportedUser(new TargetUser(userInfo))) { supportedUsers.add(userInfo); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index c40e8af73268..9c421524d723 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -68,8 +68,21 @@ public abstract class InputMethodManagerInternal { * @param autofillId {@link AutofillId} of currently focused field. * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object. */ - public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb); + public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb); + + /** + * Force switch to the enabled input method by {@code imeId} for current user. If the input + * method with {@code imeId} is not enabled or not installed, do nothing. + * + * @param imeId The input method ID to be switched to. + * @param userId The user ID to be queried. + * @return {@code true} if the current input method was successfully switched to the input + * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available + * to be switched. + */ + public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId); /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. @@ -95,8 +108,14 @@ public abstract class InputMethodManagerInternal { } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { + public void onCreateInlineSuggestionsRequest(int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb) { + } + + @Override + public boolean switchToInputMethod(String imeId, int userId) { + return false; } }; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8b6b61499e9e..0bf65bd6f739 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -111,6 +111,7 @@ import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillId; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionInspector; @@ -142,6 +143,7 @@ import com.android.internal.os.TransferPipe; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethod; import com.android.internal.view.IInputMethodClient; @@ -1790,15 +1792,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("mMethodMap") - private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback callback) { - + private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback callback) { final InputMethodInfo imi = mMethodMap.get(mCurMethodId); try { - if (imi != null && imi.isInlineSuggestionsEnabled() && mCurMethod != null) { + if (userId == mSettings.getCurrentUserId() && imi != null + && imi.isInlineSuggestionsEnabled() && mCurMethod != null) { executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod, - componentName, autofillId, callback)); + componentName, autofillId, + new InlineSuggestionsRequestCallbackDecorator(callback, + imi.getPackageName()))); } else { callback.onInlineSuggestionsUnsupported(); } @@ -1808,6 +1813,42 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** + * The decorator which validates the host package name in the + * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name. + */ + private static final class InlineSuggestionsRequestCallbackDecorator + extends IInlineSuggestionsRequestCallback.Stub { + @NonNull + private final IInlineSuggestionsRequestCallback mCallback; + @NonNull + private final String mImePackageName; + + InlineSuggestionsRequestCallbackDecorator( + @NonNull IInlineSuggestionsRequestCallback callback, + @NonNull String imePackageName) { + mCallback = callback; + mImePackageName = imePackageName; + } + + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + mCallback.onInlineSuggestionsUnsupported(); + } + + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) throws RemoteException { + if (!mImePackageName.equals(request.getHostPackageName())) { + throw new SecurityException( + "Host package name in the provide request=[" + request.getHostPackageName() + + "] doesn't match the IME package name=[" + mImePackageName + + "]."); + } + mCallback.onInlineSuggestionsRequest(request, callback); + } + } + + /** * @param imiId if null, returns enabled subtypes for the current imi * @return enabled subtypes of the specified imi */ @@ -4471,10 +4512,43 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback callback) { + private void onCreateInlineSuggestionsRequest(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback callback) { synchronized (mMethodMap) { - onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback); + onCreateInlineSuggestionsRequestLocked(userId, componentName, autofillId, callback); + } + } + + private boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + synchronized (mMethodMap) { + if (userId == mSettings.getCurrentUserId()) { + if (!mMethodMap.containsKey(imeId) + || !mSettings.getEnabledInputMethodListLocked() + .contains(mMethodMap.get(imeId))) { + return false; // IME is not is found or not enabled. + } + setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); + return true; + } + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, + methodMap, methodList); + final InputMethodSettings settings = new InputMethodSettings( + mContext.getResources(), mContext.getContentResolver(), methodMap, + userId, false); + if (!methodMap.containsKey(imeId) + || !settings.getEnabledInputMethodListLocked() + .contains(methodMap.get(imeId))) { + return false; // IME is not is found or not enabled. + } + settings.putSelectedInputMethod(imeId); + settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + return true; } } @@ -4510,9 +4584,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, + public void onCreateInlineSuggestionsRequest(int userId, ComponentName componentName, AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { - mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb); + mService.onCreateInlineSuggestionsRequest(userId, componentName, autofillId, cb); + } + + @Override + public boolean switchToInputMethod(String imeId, int userId) { + return mService.switchToInputMethod(imeId, userId); } } @@ -5065,31 +5144,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!userHasDebugPriv(userId, shellCommand)) { continue; } - boolean failedToSelectUnknownIme = false; - if (userId == mSettings.getCurrentUserId()) { - if (mMethodMap.containsKey(imeId)) { - setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); - } else { - failedToSelectUnknownIme = true; - } - } else { - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = - new ArrayMap<>(); - AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList); - final InputMethodSettings settings = new InputMethodSettings( - mContext.getResources(), mContext.getContentResolver(), methodMap, - userId, false); - if (methodMap.containsKey(imeId)) { - settings.putSelectedInputMethod(imeId); - settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); - } else { - failedToSelectUnknownIme = true; - } - } + boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId); if (failedToSelectUnknownIme) { error.print("Unknown input method "); error.print(imeId); diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 1f9379c259cf..f09795fbea01 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -191,8 +191,9 @@ public final class MultiClientInputMethodManagerService { } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { + public void onCreateInlineSuggestionsRequest(int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb) { try { //TODO(b/137800469): support multi client IMEs. cb.onInlineSuggestionsUnsupported(); @@ -200,6 +201,12 @@ public final class MultiClientInputMethodManagerService { Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e); } } + + @Override + public boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + reportNotSupported(); + return false; + } }); } diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java index e555e3e5746e..4bf8fe8d93b6 100644 --- a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java +++ b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java @@ -27,30 +27,37 @@ import java.io.InputStream; */ public class BitTrackedInputStream extends BitInputStream { - private static int sReadBitsCount; + private int mReadBitsCount; /** Constructor with byte array. */ public BitTrackedInputStream(byte[] inputStream) { super(inputStream); - sReadBitsCount = 0; + mReadBitsCount = 0; } /** Constructor with input stream. */ public BitTrackedInputStream(InputStream inputStream) { super(inputStream); - sReadBitsCount = 0; + mReadBitsCount = 0; } /** Obtains an integer value of the next {@code numOfBits}. */ @Override public int getNext(int numOfBits) throws IOException { - sReadBitsCount += numOfBits; + mReadBitsCount += numOfBits; return super.getNext(numOfBits); } /** Returns the current cursor position showing the number of bits that are read. */ public int getReadBitsCount() { - return sReadBitsCount; + return mReadBitsCount; + } + + /** + * Returns true if we can read more rules by checking whether the end index is not reached yet. + */ + public boolean canReadMoreRules(int endIndexBytes) { + return mReadBitsCount < endIndexBytes * 8; } /** @@ -59,11 +66,11 @@ public class BitTrackedInputStream extends BitInputStream { * Note that the integer parameter specifies the location in bytes -- not bits. */ public void setCursorToByteLocation(int byteLocation) throws IOException { - int bitCountToRead = byteLocation * 8 - sReadBitsCount; + int bitCountToRead = byteLocation * 8 - mReadBitsCount; if (bitCountToRead < 0) { throw new IllegalStateException("The byte position is already read."); } super.getNext(bitCountToRead); - sReadBitsCount = byteLocation * 8; + mReadBitsCount = byteLocation * 8; } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index e744326c49db..2f285631b982 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -105,8 +105,7 @@ public class RuleBinaryParser implements RuleParser { bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex()); // Read the rules until we reach the end index. - while (bitTrackedInputStream.hasNext() - && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) { + while (bitTrackedInputStream.canReadMoreRules(range.getEndIndex())) { if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { parsedRules.add(parseRule(bitTrackedInputStream)); } diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java index 8c8450e5fdc4..453fa5d84053 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java @@ -23,28 +23,28 @@ import android.annotation.Nullable; * RuleIndexingController}. */ public class RuleIndexRange { - private static int sStartIndex; - private static int sEndIndex; + private int mStartIndex; + private int mEndIndex; /** Constructor with start and end indexes. */ public RuleIndexRange(int startIndex, int endIndex) { - this.sStartIndex = startIndex; - this.sEndIndex = endIndex; + this.mStartIndex = startIndex; + this.mEndIndex = endIndex; } /** Returns the startIndex. */ public int getStartIndex() { - return sStartIndex; + return mStartIndex; } /** Returns the end index. */ public int getEndIndex() { - return sEndIndex; + return mEndIndex; } @Override public boolean equals(@Nullable Object object) { - return sStartIndex == ((RuleIndexRange) object).getStartIndex() - && sEndIndex == ((RuleIndexRange) object).getEndIndex(); + return mStartIndex == ((RuleIndexRange) object).getStartIndex() + && mEndIndex == ((RuleIndexRange) object).getEndIndex(); } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java index c9713220d6e8..03392abf478f 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java @@ -56,7 +56,7 @@ public class RuleIndexingController { * read and evaluated. */ public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) { - ArrayList<RuleIndexRange> indexRanges = new ArrayList(); + List<RuleIndexRange> indexRanges = new ArrayList<>(); // Add the range for package name indexes rules. indexRanges.add( @@ -102,7 +102,7 @@ public class RuleIndexingController { .collect(Collectors.toCollection(TreeSet::new)); String minIndex = keyTreeSet.floor(searchedKey); - String maxIndex = keyTreeSet.ceiling(searchedKey); + String maxIndex = keyTreeSet.higher(searchedKey); return new RuleIndexRange( indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex), diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index f964d4cf2724..8f53be7d87af 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -48,10 +48,11 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; +import java.util.stream.Collectors; /** A helper class to serialize rules from the {@link Rule} model to Binary representation. */ public class RuleBinarySerializer implements RuleSerializer { @@ -79,31 +80,37 @@ public class RuleBinarySerializer implements RuleSerializer { throws RuleSerializeException { try { // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, TreeMap<String, List<Rule>>> indexedRules = + Map<Integer, Map<String, List<Rule>>> indexedRules = RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); + // Serialize the rules. ByteTrackedOutputStream ruleFileByteTrackedOutputStream = new ByteTrackedOutputStream(rulesFileOutputStream); - serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream); - - Map<String, Integer> packageNameIndexes = - serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED), + LinkedHashMap<String, Integer> packageNameIndexes = + serializeRuleList( + indexedRules.get(PACKAGE_NAME_INDEXED), ruleFileByteTrackedOutputStream); - indexingFileOutputStream.write( - serializeIndexes(packageNameIndexes, /* isIndexed= */true)); - - Map<String, Integer> appCertificateIndexes = - serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED), + LinkedHashMap<String, Integer> appCertificateIndexes = + serializeRuleList( + indexedRules.get(APP_CERTIFICATE_INDEXED), ruleFileByteTrackedOutputStream); - indexingFileOutputStream.write( - serializeIndexes(appCertificateIndexes, /* isIndexed= */true)); - - Map<String, Integer> unindexedRulesIndexes = - serializeRuleList(indexedRules.get(NOT_INDEXED), + LinkedHashMap<String, Integer> unindexedRulesIndexes = + serializeRuleList( + indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream); - indexingFileOutputStream.write( - serializeIndexes(unindexedRulesIndexes, /* isIndexed= */false)); + + // Serialize their indexes. + BitOutputStream indexingBitOutputStream = new BitOutputStream(); + serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */true); + serializeIndexGroup(appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ + true); + serializeIndexGroup(unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ + false); + // TODO(b/147609625): This dummy bit is set for fixing the padding issue. Remove when + // the issue is fixed and correct the tests that does this padding too. + indexingBitOutputStream.setNext(); + indexingFileOutputStream.write(indexingBitOutputStream.toByteArray()); } catch (Exception e) { throw new RuleSerializeException(e.getMessage(), e); } @@ -119,24 +126,25 @@ public class RuleBinarySerializer implements RuleSerializer { outputStream.write(bitOutputStream.toByteArray()); } - private Map<String, Integer> serializeRuleList(TreeMap<String, List<Rule>> rulesMap, - ByteTrackedOutputStream outputStream) + private LinkedHashMap<String, Integer> serializeRuleList( + Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream) throws IOException { Preconditions.checkArgument(rulesMap != null, "serializeRuleList should never be called with null rule list."); BitOutputStream bitOutputStream = new BitOutputStream(); - Map<String, Integer> indexMapping = new TreeMap(); - int indexTracker = 0; - + LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap(); indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount()); - for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) { + + List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList()); + int indexTracker = 0; + for (String key : sortedKeys) { if (indexTracker >= INDEXING_BLOCK_SIZE) { - indexMapping.put(entry.getKey(), outputStream.getWrittenBytesCount()); + indexMapping.put(key, outputStream.getWrittenBytesCount()); indexTracker = 0; } - for (Rule rule : entry.getValue()) { + for (Rule rule : rulesMap.get(key)) { bitOutputStream.clear(); serializeRule(rule, bitOutputStream); outputStream.write(bitOutputStream.toByteArray()); @@ -220,12 +228,14 @@ public class RuleBinarySerializer implements RuleSerializer { } } - private byte[] serializeIndexes(Map<String, Integer> indexes, boolean isIndexed) { - BitOutputStream bitOutputStream = new BitOutputStream(); + private void serializeIndexGroup( + LinkedHashMap<String, Integer> indexes, + BitOutputStream bitOutputStream, + boolean isIndexed) { // Output the starting location of this indexing group. - serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false, - bitOutputStream); + serializeStringValue( + START_INDEXING_KEY, /* isHashedValue= */false, bitOutputStream); serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream); // If the group is indexed, output the locations of the indexes. @@ -243,8 +253,6 @@ public class RuleBinarySerializer implements RuleSerializer { // Output the end location of this indexing group. serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream); serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream); - - return bitOutputStream.toByteArray(); } private void serializeStringValue( diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java index dd871e2bbe6c..2cbd4ede5214 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java @@ -28,6 +28,8 @@ class RuleIndexingDetails { static final int PACKAGE_NAME_INDEXED = 1; static final int APP_CERTIFICATE_INDEXED = 2; + static final String DEFAULT_RULE_KEY = "N/A"; + /** Represents which indexed file the rule should be located. */ @IntDef( value = { @@ -45,7 +47,7 @@ class RuleIndexingDetails { /** Constructor without a ruleKey for {@code NOT_INDEXED}. */ RuleIndexingDetails(@IndexType int indexType) { this.mIndexType = indexType; - this.mRuleKey = null; + this.mRuleKey = DEFAULT_RULE_KEY; } /** Constructor with a ruleKey for indexed rules. */ diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java index cbc365e2c250..7d9a90188983 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java @@ -30,30 +30,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; /** A helper class for identifying the indexing type and key of a given rule. */ class RuleIndexingDetailsIdentifier { - private static final String DEFAULT_RULE_KEY = "N/A"; - /** * Splits a given rule list into three indexing categories. Each rule category is returned as a * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for * NOT_INDEXED rules. */ - public static Map<Integer, TreeMap<String, List<Rule>>> splitRulesIntoIndexBuckets( + public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets( List<Rule> rules) { if (rules == null) { throw new IllegalArgumentException( "Index buckets cannot be created for null rule list."); } - Map<Integer, TreeMap<String, List<Rule>>> typeOrganizedRuleMap = new HashMap(); - typeOrganizedRuleMap.put(NOT_INDEXED, new TreeMap()); - typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new TreeMap()); - typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new TreeMap()); + Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap(); + typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap()); + typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>()); + typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>()); // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the // entries sorted by their index key. @@ -66,21 +63,14 @@ class RuleIndexingDetailsIdentifier { String.format("Malformed rule identified. [%s]", rule.toString())); } - String ruleKey = - indexingDetails.getIndexType() != NOT_INDEXED - ? indexingDetails.getRuleKey() - : DEFAULT_RULE_KEY; + int ruleIndexType = indexingDetails.getIndexType(); + String ruleKey = indexingDetails.getRuleKey(); - if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) { - typeOrganizedRuleMap - .get(indexingDetails.getIndexType()) - .put(ruleKey, new ArrayList()); + if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) { + typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList()); } - typeOrganizedRuleMap - .get(indexingDetails.getIndexType()) - .get(ruleKey) - .add(rule); + typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule); } return typeOrganizedRuleMap; diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java index 4194432375b8..8f164e645434 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java @@ -35,7 +35,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; +import java.util.stream.Collectors; /** A helper class to serialize rules from the {@link Rule} model to Xml representation. */ public class RuleXmlSerializer implements RuleSerializer { @@ -90,7 +90,7 @@ public class RuleXmlSerializer implements RuleSerializer { throws RuleSerializeException { try { // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, TreeMap<String, List<Rule>>> indexedRules = + Map<Integer, Map<String, List<Rule>>> indexedRules = RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); // Write the XML formatted rules in order. @@ -107,11 +107,12 @@ public class RuleXmlSerializer implements RuleSerializer { } } - private void serializeRuleList(TreeMap<String, List<Rule>> rulesMap, - XmlSerializer xmlSerializer) + private void serializeRuleList(Map<String, List<Rule>> rulesMap, XmlSerializer xmlSerializer) throws IOException { - for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) { - for (Rule rule : entry.getValue()) { + List<String> sortedKeyList = + rulesMap.keySet().stream().sorted().collect(Collectors.toList()); + for (String key : sortedKeyList) { + for (Rule rule : rulesMap.get(key)) { serializeRule(rule, xmlSerializer); } } diff --git a/services/core/java/com/android/server/location/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java index 67841aca1605..d2296ea27913 100644 --- a/services/core/java/com/android/server/location/NtpTimeHelper.java +++ b/services/core/java/com/android/server/location/NtpTimeHelper.java @@ -130,7 +130,8 @@ class NtpTimeHelper { // force refresh NTP cache when outdated boolean refreshSuccess = true; - if (mNtpTime.getCacheAge() >= NTP_INTERVAL) { + NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult(); + if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) { // Blocking network operation. refreshSuccess = mNtpTime.forceRefresh(); } @@ -140,17 +141,17 @@ class NtpTimeHelper { // only update when NTP time is fresh // If refreshSuccess is false, cacheAge does not drop down. - if (mNtpTime.getCacheAge() < NTP_INTERVAL) { - long time = mNtpTime.getCachedNtpTime(); - long timeReference = mNtpTime.getCachedNtpTimeReference(); - long certainty = mNtpTime.getCacheCertainty(); + ntpResult = mNtpTime.getCachedTimeResult(); + if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) { + long time = ntpResult.getTimeMillis(); + long timeReference = ntpResult.getElapsedRealtimeMillis(); + long certainty = ntpResult.getCertaintyMillis(); if (DEBUG) { long now = System.currentTimeMillis(); Log.d(TAG, "NTP server returned: " - + time + " (" + new Date(time) - + ") reference: " + timeReference - + " certainty: " + certainty + + time + " (" + new Date(time) + ")" + + " ntpResult: " + ntpResult + " system time offset: " + (time - now)); } diff --git a/services/core/java/com/android/server/location/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java new file mode 100644 index 000000000000..550f51c7de58 --- /dev/null +++ b/services/core/java/com/android/server/location/UserInfoStore.java @@ -0,0 +1,226 @@ +/* + * 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.location; + +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; +import com.android.server.FgThread; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provides accessors and listeners for all user info. + */ +public class UserInfoStore { + + /** + * Listener for current user changes. + */ + public interface UserChangedListener { + /** + * Called when the current user changes. + */ + void onUserChanged(@UserIdInt int oldUserId, @UserIdInt int newUserId); + } + + private final Context mContext; + private final CopyOnWriteArrayList<UserChangedListener> mListeners; + + @GuardedBy("this") + @Nullable + private UserManager mUserManager; + + @GuardedBy("this") + @UserIdInt + private int mCurrentUserId; + + @GuardedBy("this") + @UserIdInt + private int mCachedParentUserId; + @GuardedBy("this") + private int[] mCachedProfileUserIds; + + public UserInfoStore(Context context) { + mContext = context; + mListeners = new CopyOnWriteArrayList<>(); + + mCurrentUserId = UserHandle.USER_NULL; + mCachedParentUserId = UserHandle.USER_NULL; + mCachedProfileUserIds = new int[]{UserHandle.USER_NULL}; + } + + /** Called when system is ready. */ + public synchronized void onSystemReady() { + if (mUserManager != null) { + return; + } + + mUserManager = mContext.getSystemService(UserManager.class); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_SWITCHED); + intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); + + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + switch (action) { + case Intent.ACTION_USER_SWITCHED: + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, + UserHandle.USER_NULL); + if (userId != UserHandle.USER_NULL) { + onUserChanged(userId); + } + break; + case Intent.ACTION_MANAGED_PROFILE_ADDED: + case Intent.ACTION_MANAGED_PROFILE_REMOVED: + onUserProfilesChanged(); + break; + } + } + }, UserHandle.ALL, intentFilter, null, FgThread.getHandler()); + + mCurrentUserId = ActivityManager.getCurrentUser(); + } + + /** + * Adds a listener for user changed events. + */ + public void addListener(UserChangedListener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener for user changed events. + */ + public void removeListener(UserChangedListener listener) { + mListeners.remove(listener); + } + + private void onUserChanged(@UserIdInt int newUserId) { + int oldUserId; + synchronized (this) { + if (newUserId == mCurrentUserId) { + return; + } + + oldUserId = mCurrentUserId; + mCurrentUserId = newUserId; + } + + for (UserChangedListener listener : mListeners) { + listener.onUserChanged(oldUserId, newUserId); + } + } + + private synchronized void onUserProfilesChanged() { + // this intent is only sent to the current user + if (mCachedParentUserId == mCurrentUserId) { + mCachedParentUserId = UserHandle.USER_NULL; + mCachedProfileUserIds = null; + } + } + + /** + * Returns the user id of the current user. + */ + @UserIdInt + public synchronized int getCurrentUserId() { + return mCurrentUserId; + } + + /** + * Returns true if the given user id is either the current user or a profile of the current + * user. + */ + public synchronized boolean isCurrentUserOrProfile(@UserIdInt int userId) { + return userId == mCurrentUserId || ArrayUtils.contains( + getProfileUserIdsForParentUser(mCurrentUserId), userId); + } + + /** + * Returns the parent user id of the given user id, or the user id itself if the user id either + * is a parent or has no profiles. + */ + @UserIdInt + public synchronized int getParentUserId(@UserIdInt int userId) { + int parentUserId; + if (userId == mCachedParentUserId || ArrayUtils.contains(mCachedProfileUserIds, userId)) { + parentUserId = mCachedParentUserId; + } else { + Preconditions.checkState(mUserManager != null); + + UserInfo userInfo = mUserManager.getProfileParent(userId); + if (userInfo != null) { + parentUserId = userInfo.id; + } else { + // getProfileParent() returns null if the userId is already the parent... + parentUserId = userId; + } + + // force profiles into cache + getProfileUserIdsForParentUser(parentUserId); + } + + return parentUserId; + } + + @GuardedBy("this") + private int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) { + Preconditions.checkState(mUserManager != null); + + if (Build.IS_DEBUGGABLE) { + Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null); + } + + if (parentUserId != mCachedParentUserId) { + mCachedParentUserId = parentUserId; + mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId); + } + + return mCachedProfileUserIds; + } + + /** + * Dump info for debugging. + */ + public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Current User: " + mCurrentUserId + " " + Arrays.toString( + getProfileUserIdsForParentUser(mCurrentUserId))); + } +} diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index d2e54f9cd64c..46ea9d11d1dc 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -25,11 +25,13 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserManager; import android.util.Slog; +import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.RebootEscrowListener; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicBoolean; @@ -109,20 +111,50 @@ class RebootEscrowManager { } void loadRebootEscrowDataIfAvailable() { + List<UserInfo> users = mUserManager.getUsers(); + List<UserInfo> rebootEscrowUsers = new ArrayList<>(); + for (UserInfo user : users) { + if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { + rebootEscrowUsers.add(user); + } + } + + if (rebootEscrowUsers.isEmpty()) { + return; + } + + SecretKeySpec escrowKey = getAndClearRebootEscrowKey(); + if (escrowKey == null) { + Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); + for (UserInfo user : users) { + mStorage.removeRebootEscrow(user.id); + } + StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false); + return; + } + + boolean allUsersUnlocked = true; + for (UserInfo user : rebootEscrowUsers) { + allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey); + } + StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked); + } + + private SecretKeySpec getAndClearRebootEscrowKey() { IRebootEscrow rebootEscrow = mInjector.getRebootEscrow(); if (rebootEscrow == null) { - return; + return null; } - final SecretKeySpec escrowKey; try { byte[] escrowKeyBytes = rebootEscrow.retrieveKey(); if (escrowKeyBytes == null) { - return; + Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key"); + return null; } else if (escrowKeyBytes.length != 32) { Slog.e(TAG, "IRebootEscrow returned key of incorrect size " + escrowKeyBytes.length); - return; + return null; } // Make sure we didn't get the null key. @@ -132,29 +164,22 @@ class RebootEscrowManager { } if (zero == 0) { Slog.w(TAG, "IRebootEscrow returned an all-zeroes key"); - return; + return null; } // Overwrite the existing key with the null key rebootEscrow.storeKey(new byte[32]); - escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes); + return RebootEscrowData.fromKeyBytes(escrowKeyBytes); } catch (RemoteException e) { Slog.w(TAG, "Could not retrieve escrow data"); - return; - } - - List<UserInfo> users = mUserManager.getUsers(); - for (UserInfo user : users) { - if (mCallbacks.isUserSecure(user.id)) { - restoreRebootEscrowForUser(user.id, escrowKey); - } + return null; } } - private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) { + private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) { if (!mStorage.hasRebootEscrow(userId)) { - return; + return false; } try { @@ -165,9 +190,11 @@ class RebootEscrowManager { mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(), escrowData.getSyntheticPassword(), userId); + return true; } catch (IOException e) { Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e); } + return false; } void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion, diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 29338ba06dc2..52750f392d5b 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L import android.content.Context; import android.os.RemoteException; +import android.os.UserHandle; import android.security.Scrypt; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; @@ -163,16 +164,28 @@ public class KeySyncTask implements Runnable { } private void syncKeys() throws RemoteException { + int generation = mPlatformKeyManager.getGenerationId(mUserId); if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // Application keys for the user will not be available for sync. Log.w(TAG, "Credentials are not set for user " + mUserId); - int generation = mPlatformKeyManager.getGenerationId(mUserId); - mPlatformKeyManager.invalidatePlatformKey(mUserId, generation); + if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED + || mUserId != UserHandle.USER_SYSTEM) { + // Only invalidate keys with legacy protection param. + mPlatformKeyManager.invalidatePlatformKey(mUserId, generation); + } return; } if (isCustomLockScreen()) { - Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId); - mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId); + Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId); + // Keys will be synced when user starts using non custom screen lock. + if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED + || mUserId != UserHandle.USER_SYSTEM) { + mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId); + } + return; + } + if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) { + Log.w(TAG, "Can't sync keys for locked user " + mUserId); return; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java index 0ad6c2a69556..0761cde825b6 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -67,8 +67,9 @@ import javax.crypto.spec.GCMParameterSpec; * @hide */ public class PlatformKeyManager { - private static final String TAG = "PlatformKeyManager"; + static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000; + private static final String TAG = "PlatformKeyManager"; private static final String KEY_ALGORITHM = "AES"; private static final int KEY_SIZE_BITS = 256; private static final String KEY_ALIAS_PREFIX = @@ -131,14 +132,14 @@ public class PlatformKeyManager { /** * Returns {@code true} if the platform key is available. A platform key won't be available if - * the user has not set up a lock screen. + * device is locked. * * @param userId The ID of the user to whose lock screen the platform key must be bound. * * @hide */ - public boolean isAvailable(int userId) { - return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId); + public boolean isDeviceLocked(int userId) { + return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId); } /** @@ -169,7 +170,6 @@ public class PlatformKeyManager { * @param userId The ID of the user to whose lock screen the platform key must be bound. * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. * @throws KeyStoreException if there is an error in AndroidKeyStore. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -177,13 +177,8 @@ public class PlatformKeyManager { */ @VisibleForTesting void regenerate(int userId) - throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException, + throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException { - if (!isAvailable(userId)) { - throw new InsecureUserException(String.format( - Locale.US, "%d does not have a lock screen set.", userId)); - } - int generationId = getGenerationId(userId); int nextId; if (generationId == -1) { @@ -192,6 +187,7 @@ public class PlatformKeyManager { invalidatePlatformKey(userId, generationId); nextId = generationId + 1; } + generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED); generateAndLoadKey(userId, nextId); } @@ -203,7 +199,6 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -211,7 +206,7 @@ public class PlatformKeyManager { */ public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, - InsecureUserException, IOException, RemoteException { + IOException, RemoteException { init(userId); try { // Try to see if the decryption key is still accessible before using the encryption key. @@ -234,12 +229,11 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * * @hide */ private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException, - UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { + UnrecoverableKeyException, NoSuchAlgorithmException { int generationId = getGenerationId(userId); String alias = getEncryptAlias(userId, generationId); if (!isKeyLoaded(userId, generationId)) { @@ -258,7 +252,6 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -266,7 +259,7 @@ public class PlatformKeyManager { */ public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, - InsecureUserException, IOException, RemoteException { + IOException, RemoteException { init(userId); try { PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId); @@ -288,12 +281,11 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * * @hide */ private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException, - UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { + UnrecoverableKeyException, NoSuchAlgorithmException { int generationId = getGenerationId(userId); String alias = getDecryptAlias(userId, generationId); if (!isKeyLoaded(userId, generationId)) { @@ -340,13 +332,8 @@ public class PlatformKeyManager { * @hide */ void init(int userId) - throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException, + throws KeyStoreException, NoSuchAlgorithmException, IOException, RemoteException { - if (!isAvailable(userId)) { - throw new InsecureUserException(String.format( - Locale.US, "%d does not have a lock screen set.", userId)); - } - int generationId = getGenerationId(userId); if (isKeyLoaded(userId, generationId)) { Log.i(TAG, String.format( @@ -363,6 +350,7 @@ public class PlatformKeyManager { generationId++; } + generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED); generateAndLoadKey(userId, generationId); } @@ -440,12 +428,16 @@ public class PlatformKeyManager { KeyProtection.Builder decryptionKeyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) - .setUserAuthenticationRequired(true) - .setUserAuthenticationValidityDurationSeconds( - USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE); - if (userId != UserHandle.USER_SYSTEM) { + // Skip UserAuthenticationRequired for main user + if (userId == UserHandle.USER_SYSTEM) { + decryptionKeyProtection.setUnlockedDeviceRequired(true); + } else { + // With setUnlockedDeviceRequired, KeyStore thinks that device is locked . + decryptionKeyProtection.setUserAuthenticationRequired(true); + decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds( + USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS); // Bind decryption key to secondary profile lock screen secret. long secureUserId = getGateKeeperService().getSecureUserId(userId); // TODO(b/124095438): Propagate this failure instead of silently failing. diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 383d5cf326c0..6d97ed7a69a7 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -19,7 +19,6 @@ package com.android.server.locksettings.recoverablekeystore; import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED; import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE; -import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING; @@ -46,7 +45,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; -import com.android.internal.util.Preconditions; import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils; import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException; @@ -76,8 +74,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.crypto.AEADBadTagException; @@ -89,13 +88,14 @@ import javax.crypto.AEADBadTagException; */ public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreMgr"; + private static final long SYNC_DELAY_MILLIS = 2000; private static RecoverableKeyStoreManager mInstance; private final Context mContext; private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; - private final ExecutorService mExecutorService; + private final ScheduledExecutorService mExecutorService; private final RecoverySnapshotListenersStorage mListenersStorage; private final RecoverableKeyGenerator mRecoverableKeyGenerator; private final RecoverySnapshotStorage mSnapshotStorage; @@ -136,7 +136,7 @@ public class RecoverableKeyStoreManager { context.getApplicationContext(), db, new RecoverySessionStorage(), - Executors.newSingleThreadExecutor(), + Executors.newScheduledThreadPool(1), snapshotStorage, new RecoverySnapshotListenersStorage(), platformKeyManager, @@ -152,7 +152,7 @@ public class RecoverableKeyStoreManager { Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, - ExecutorService executorService, + ScheduledExecutorService executorService, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, @@ -724,8 +724,6 @@ public class RecoverableKeyStoreManager { throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } catch (InsecureUserException e) { - throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { @@ -793,8 +791,6 @@ public class RecoverableKeyStoreManager { throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } catch (InsecureUserException e) { - throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { @@ -915,7 +911,7 @@ public class RecoverableKeyStoreManager { int storedHashType, @NonNull byte[] credential, int userId) { // So as not to block the critical path unlocking the phone, defer to another thread. try { - mExecutorService.execute(KeySyncTask.newInstance( + mExecutorService.schedule(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, @@ -923,7 +919,10 @@ public class RecoverableKeyStoreManager { userId, storedHashType, credential, - /*credentialUpdated=*/ false)); + /*credentialUpdated=*/ false), + SYNC_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { @@ -947,7 +946,7 @@ public class RecoverableKeyStoreManager { int userId) { // So as not to block the critical path unlocking the phone, defer to another thread. try { - mExecutorService.execute(KeySyncTask.newInstance( + mExecutorService.schedule(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, @@ -955,7 +954,10 @@ public class RecoverableKeyStoreManager { userId, storedHashType, credential, - /*credentialUpdated=*/ true)); + /*credentialUpdated=*/ true), + SYNC_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java new file mode 100644 index 000000000000..5191833f367e --- /dev/null +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -0,0 +1,321 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.MediaRoute2Info; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseBooleanArray; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +class BluetoothRouteProvider { + private static final String TAG = "BTRouteProvider"; + private static BluetoothRouteProvider sInstance; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>(); + @SuppressWarnings("WeakerAccess") /* synthetic access */ + BluetoothA2dp mA2dpProfile; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + BluetoothHearingAid mHearingAidProfile; + + private final Context mContext; + private final BluetoothAdapter mBluetoothAdapter; + private final BluetoothRoutesUpdatedListener mListener; + private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>(); + private final IntentFilter mIntentFilter = new IntentFilter(); + private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver(); + private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener(); + + private BluetoothDevice mActiveDevice = null; + + static synchronized BluetoothRouteProvider getInstance(@NonNull Context context, + @NonNull BluetoothRoutesUpdatedListener listener) { + Objects.requireNonNull(context); + Objects.requireNonNull(listener); + + if (sInstance == null) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter == null) { + return null; + } + sInstance = new BluetoothRouteProvider(context, btAdapter, listener); + } + return sInstance; + } + + private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter, + BluetoothRoutesUpdatedListener listener) { + mContext = context; + mBluetoothAdapter = btAdapter; + mListener = listener; + buildBluetoothRoutes(); + + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID); + + // Bluetooth on/off broadcasts + addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver()); + + // Pairing broadcasts + addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver()); + + DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier(); + addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver); + addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver); + addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED, + deviceStateChangedReceiver); + addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED, + deviceStateChangedReceiver); + + mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null); + } + + private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) { + mEventReceiverMap.put(action, eventReceiver); + mIntentFilter.addAction(action); + } + + private void buildBluetoothRoutes() { + mBluetoothRoutes.clear(); + for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) { + if (device.isConnected()) { + BluetoothRouteInfo newBtRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), newBtRoute); + } + } + } + + @NonNull List<MediaRoute2Info> getBluetoothRoutes() { + ArrayList<MediaRoute2Info> routes = new ArrayList<>(); + for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { + routes.add(btRoute.route); + } + return routes; + } + + private void notifyBluetoothRoutesUpdated() { + if (mListener != null) { + mListener.onBluetoothRoutesUpdated(getBluetoothRoutes()); + } + } + + private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { + BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); + newBtRoute.btDevice = device; + newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName()) + .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) + .setDescription(mContext.getResources().getText( + R.string.bluetooth_a2dp_audio_route_name).toString()) + .build(); + newBtRoute.connectedProfiles = new SparseBooleanArray(); + return newBtRoute; + } + + private void setRouteConnectionStateForDevice(BluetoothDevice device, + @MediaRoute2Info.ConnectionState int state) { + if (device == null) { + Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null"); + return; + } + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (btRoute == null) { + Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null"); + return; + } + if (btRoute.route.getConnectionState() != state) { + btRoute.route = new MediaRoute2Info.Builder(btRoute.route) + .setConnectionState(state).build(); + } + } + + interface BluetoothRoutesUpdatedListener { + void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes); + } + + private class BluetoothRouteInfo { + public BluetoothDevice btDevice; + public MediaRoute2Info route; + public SparseBooleanArray connectedProfiles; + } + + // These callbacks run on the main thread. + private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dpProfile = (BluetoothA2dp) proxy; + break; + case BluetoothProfile.HEARING_AID: + mHearingAidProfile = (BluetoothHearingAid) proxy; + break; + default: + return; + } + for (BluetoothDevice device : proxy.getConnectedDevices()) { + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (btRoute == null) { + btRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), btRoute); + } + btRoute.connectedProfiles.put(profile, true); + } + } + + public void onServiceDisconnected(int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dpProfile = null; + break; + case BluetoothProfile.HEARING_AID: + mHearingAidProfile = null; + break; + default: + return; + } + } + } + private class BluetoothBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + BluetoothEventReceiver receiver = mEventReceiverMap.get(action); + if (receiver != null) { + receiver.onReceive(context, intent, device); + } + } + } + + private interface BluetoothEventReceiver { + void onReceive(Context context, Intent intent, BluetoothDevice device); + } + + private class AdapterStateChangedReceiver implements BluetoothEventReceiver { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); + if (state == BluetoothAdapter.STATE_OFF + || state == BluetoothAdapter.STATE_TURNING_OFF) { + mBluetoothRoutes.clear(); + notifyBluetoothRoutesUpdated(); + } else if (state == BluetoothAdapter.STATE_ON) { + buildBluetoothRoutes(); + if (!mBluetoothRoutes.isEmpty()) { + notifyBluetoothRoutesUpdated(); + } + } + } + } + + private class BondStateChangedReceiver implements BluetoothEventReceiver { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) { + btRoute = createBluetoothRoute(device); + if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) { + btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true); + } + if (mHearingAidProfile != null + && mHearingAidProfile.getConnectedDevices().contains(device)) { + btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true); + } + mBluetoothRoutes.put(device.getAddress(), btRoute); + notifyBluetoothRoutesUpdated(); + } else if (bondState == BluetoothDevice.BOND_NONE + && mBluetoothRoutes.remove(device.getAddress()) != null) { + notifyBluetoothRoutesUpdated(); + } + } + } + + private class DeviceStateChangedRecevier implements BluetoothEventReceiver { + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + switch (intent.getAction()) { + case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: + String prevActiveDeviceAddress = + (mActiveDevice == null) ? null : mActiveDevice.getAddress(); + String curActiveDeviceAddress = + (device == null) ? null : device.getAddress(); + if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) { + if (mActiveDevice != null) { + setRouteConnectionStateForDevice(mActiveDevice, + MediaRoute2Info.CONNECTION_STATE_DISCONNECTED); + } + if (device != null) { + setRouteConnectionStateForDevice(device, + MediaRoute2Info.CONNECTION_STATE_CONNECTED); + } + notifyBluetoothRoutesUpdated(); + mActiveDevice = device; + } + break; + case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: + handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device); + break; + } + } + + private void handleConnectionStateChanged(int profile, Intent intent, + BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (state == BluetoothProfile.STATE_CONNECTED) { + if (btRoute == null) { + btRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), btRoute); + btRoute.connectedProfiles.put(profile, true); + notifyBluetoothRoutesUpdated(); + } else { + btRoute.connectedProfiles.put(profile, true); + } + } else if (state == BluetoothProfile.STATE_DISCONNECTING + || state == BluetoothProfile.STATE_DISCONNECTED) { + btRoute.connectedProfiles.delete(profile); + if (btRoute.connectedProfiles.size() == 0) { + mBluetoothRoutes.remove(device.getAddress()); + if (mActiveDevice != null + && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) { + mActiveDevice = null; + } + notifyBluetoothRoutesUpdated(); + } + } + } + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 9c9a4121830f..408c1c9b7d18 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,20 +21,24 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.media.MediaRoute2ProviderInfo; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; + +import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; abstract class MediaRoute2Provider { final ComponentName mComponentName; final String mUniqueId; + final Object mLock = new Object(); Callback mCallback; private volatile MediaRoute2ProviderInfo mProviderInfo; - private volatile List<RouteSessionInfo> mSessionInfos = Collections.emptyList(); + + @GuardedBy("mLock") + final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>(); MediaRoute2Provider(@NonNull ComponentName componentName) { mComponentName = Objects.requireNonNull(componentName, "Component name must not be null."); @@ -68,12 +72,13 @@ abstract class MediaRoute2Provider { } @NonNull - public List<RouteSessionInfo> getSessionInfos() { - return mSessionInfos; + public List<RoutingSessionInfo> getSessionInfos() { + synchronized (mLock) { + return mSessionInfos; + } } - void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo, - List<RouteSessionInfo> sessionInfos) { + void setProviderState(MediaRoute2ProviderInfo providerInfo) { if (providerInfo == null) { mProviderInfo = null; } else { @@ -81,20 +86,19 @@ abstract class MediaRoute2Provider { .setUniqueId(mUniqueId) .build(); } - List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>(); - for (RouteSessionInfo sessionInfo : sessionInfos) { - sessionInfoWithProviderId.add( - new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(mUniqueId) - .build()); - } - mSessionInfos = sessionInfoWithProviderId; + } + void notifyProviderState() { if (mCallback != null) { mCallback.onProviderStateChanged(this); } } + void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) { + setProviderState(providerInfo); + notifyProviderState(); + } + public boolean hasComponentName(String packageName, String className) { return mComponentName.getPackageName().equals(packageName) && mComponentName.getClassName().equals(className); @@ -103,12 +107,11 @@ abstract class MediaRoute2Provider { public interface Callback { void onProviderStateChanged(@Nullable MediaRoute2Provider provider); void onSessionCreated(@NonNull MediaRoute2Provider provider, - @Nullable RouteSessionInfo sessionInfo, long requestId); - // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes. - void onSessionInfoChanged(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo); - // TODO: Call this when service actually notifies of session release. + @Nullable RoutingSessionInfo sessionInfo, long requestId); + void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId); + void onSessionUpdated(@NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo); void onSessionReleased(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo); + @NonNull RoutingSessionInfo sessionInfo); } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 635983575226..3840d0206016 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -17,7 +17,6 @@ package com.android.server.media; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -26,7 +25,7 @@ import android.media.IMediaRoute2Provider; import android.media.IMediaRoute2ProviderClient; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -37,8 +36,6 @@ import android.util.Slog; import java.io.PrintWriter; import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.List; import java.util.Objects; /** @@ -270,44 +267,126 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } private void onProviderStateUpdated(Connection connection, - MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) { + MediaRoute2ProviderInfo providerInfo) { if (mActiveConnection != connection) { return; } if (DEBUG) { Slog.d(TAG, this + ": State changed "); } - setAndNotifyProviderState(providerInfo, sessionInfos); + setAndNotifyProviderState(providerInfo); } - private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo, + private void onSessionCreated(Connection connection, RoutingSessionInfo sessionInfo, long requestId) { if (mActiveConnection != connection) { return; } - if (sessionInfo != null) { - sessionInfo = new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + + if (sessionInfo == null) { + Slog.w(TAG, "onSessionCreated: Ignoring null sessionInfo sent from " + mComponentName); + return; } + + sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .setProviderId(getUniqueId()) + .build(); + + boolean duplicateSessionAlreadyExists = false; + synchronized (mLock) { + for (int i = 0; i < mSessionInfos.size(); i++) { + if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) { + duplicateSessionAlreadyExists = true; + break; + } + } + mSessionInfos.add(sessionInfo); + } + + if (duplicateSessionAlreadyExists) { + Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring."); + return; + } + mCallback.onSessionCreated(this, sessionInfo, requestId); } - private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) { + private void onSessionCreationFailed(Connection connection, long requestId) { + if (mActiveConnection != connection) { + return; + } + + if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) { + Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_UNKNOWN"); + return; + } + + mCallback.onSessionCreationFailed(this, requestId); + } + + private void onSessionUpdated(Connection connection, RoutingSessionInfo sessionInfo) { if (mActiveConnection != connection) { return; } if (sessionInfo == null) { - Slog.w(TAG, "onSessionInfoChanged: Ignoring null sessionInfo sent from " + Slog.w(TAG, "onSessionUpdated: Ignoring null sessionInfo sent from " + mComponentName); return; } - sessionInfo = new RouteSessionInfo.Builder(sessionInfo) + sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .setProviderId(getUniqueId()) .build(); - mCallback.onSessionInfoChanged(this, sessionInfo); + boolean found = false; + synchronized (mLock) { + for (int i = 0; i < mSessionInfos.size(); i++) { + if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) { + mSessionInfos.set(i, sessionInfo); + found = true; + break; + } + } + } + + if (!found) { + Slog.w(TAG, "onSessionUpdated: Matching session info not found"); + return; + } + + mCallback.onSessionUpdated(this, sessionInfo); + } + + private void onSessionReleased(Connection connection, RoutingSessionInfo sessionInfo) { + if (mActiveConnection != connection) { + return; + } + if (sessionInfo == null) { + Slog.w(TAG, "onSessionReleased: Ignoring null sessionInfo sent from " + mComponentName); + return; + } + + sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .setProviderId(getUniqueId()) + .build(); + + boolean found = false; + synchronized (mLock) { + for (int i = 0; i < mSessionInfos.size(); i++) { + if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) { + mSessionInfos.remove(i); + found = true; + break; + } + } + } + + if (!found) { + Slog.w(TAG, "onSessionReleased: Matching session info not found"); + return; + } + + mCallback.onSessionReleased(this, sessionInfo); } private void disconnect() { @@ -315,7 +394,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mConnectionReady = false; mActiveConnection.dispose(); mActiveConnection = null; - setAndNotifyProviderState(null, Collections.emptyList()); + setAndNotifyProviderState(null); } } @@ -421,19 +500,24 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mHandler.post(() -> onConnectionDied(Connection.this)); } - void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo, - List<RouteSessionInfo> sessionInfos) { - mHandler.post(() -> onProviderStateUpdated(Connection.this, - providerInfo, sessionInfos)); + void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo) { + mHandler.post(() -> onProviderStateUpdated(Connection.this, providerInfo)); } - void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { - mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, - requestId)); + void postSessionCreated(RoutingSessionInfo sessionInfo, long requestId) { + mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, requestId)); } - void postSessionInfoChanged(RouteSessionInfo sessionInfo) { - mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo)); + void postSessionCreationFailed(long requestId) { + mHandler.post(() -> onSessionCreationFailed(Connection.this, requestId)); + } + + void postSessionUpdated(RoutingSessionInfo sessionInfo) { + mHandler.post(() -> onSessionUpdated(Connection.this, sessionInfo)); + } + + void postSessionReleased(RoutingSessionInfo sessionInfo) { + mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo)); } } @@ -449,16 +533,15 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void updateState(MediaRoute2ProviderInfo providerInfo, - List<RouteSessionInfo> sessionInfos) { + public void updateState(MediaRoute2ProviderInfo providerInfo) { Connection connection = mConnectionRef.get(); if (connection != null) { - connection.postProviderStateUpdated(providerInfo, sessionInfos); + connection.postProviderStateUpdated(providerInfo); } } @Override - public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { + public void notifySessionCreated(RoutingSessionInfo sessionInfo, long requestId) { Connection connection = mConnectionRef.get(); if (connection != null) { connection.postSessionCreated(sessionInfo, requestId); @@ -466,10 +549,26 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) { + public void notifySessionCreationFailed(long requestId) { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postSessionCreationFailed(requestId); + } + } + + @Override + public void notifySessionUpdated(RoutingSessionInfo sessionInfo) { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postSessionUpdated(sessionInfo); + } + } + + @Override + public void notifySessionReleased(RoutingSessionInfo sessionInfo) { Connection connection = mConnectionRef.get(); if (connection != null) { - connection.postSessionInfoChanged(sessionInfo); + connection.postSessionReleased(sessionInfo); } } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 487ab5201278..d940e3587794 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -22,7 +22,6 @@ import static android.media.MediaRouter2Utils.getProviderId; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; @@ -31,8 +30,9 @@ import android.media.IMediaRouter2Client; import android.media.IMediaRouter2Manager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.MediaRoute2ProviderService; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -178,18 +178,18 @@ class MediaRouter2ServiceImpl { } public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route, - String routeType, int requestId) { + String routeFeature, int requestId) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(routeType)) { - throw new IllegalArgumentException("routeType must not be empty"); + if (TextUtils.isEmpty(routeFeature)) { + throw new IllegalArgumentException("routeFeature must not be empty"); } final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestCreateSessionLocked(client, route, routeType, requestId); + requestCreateSessionLocked(client, route, routeFeature, requestId); } } finally { Binder.restoreCallingIdentity(token); @@ -284,15 +284,15 @@ class MediaRouter2ServiceImpl { } public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client, - @NonNull RouteDiscoveryRequest request) { + @NonNull RouteDiscoveryPreference preference) { Objects.requireNonNull(client, "client must not be null"); - Objects.requireNonNull(request, "request must not be null"); + Objects.requireNonNull(preference, "preference must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { Client2Record clientRecord = mAllClientRecords.get(client.asBinder()); - setDiscoveryRequestLocked(clientRecord, request); + setDiscoveryRequestLocked(clientRecord, preference); } } finally { Binder.restoreCallingIdentity(token); @@ -370,7 +370,7 @@ class MediaRouter2ServiceImpl { } @NonNull - public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { + public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -450,7 +450,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client, - @NonNull MediaRoute2Info route, @NonNull String routeType, long requestId) { + @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) { final IBinder binder = client.asBinder(); final Client2Record clientRecord = mAllClientRecords.get(binder); @@ -463,7 +463,7 @@ class MediaRouter2ServiceImpl { clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::requestCreateSessionOnHandler, clientRecord.mUserRecord.mHandler, - clientRecord, route, routeType, requestId)); + clientRecord, route, routeFeature, requestId)); } } @@ -519,13 +519,13 @@ class MediaRouter2ServiceImpl { } private void setDiscoveryRequestLocked(Client2Record clientRecord, - RouteDiscoveryRequest discoveryRequest) { + RouteDiscoveryPreference discoveryRequest) { if (clientRecord != null) { - if (clientRecord.mDiscoveryRequest.equals(discoveryRequest)) { + if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) { return; } - clientRecord.mDiscoveryRequest = discoveryRequest; + clientRecord.mDiscoveryPreference = discoveryRequest; clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateClientUsage, clientRecord.mUserRecord.mHandler, clientRecord)); @@ -622,9 +622,9 @@ class MediaRouter2ServiceImpl { } long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId); if (clientRecord != null && managerRecord.mTrusted) { - //TODO: select route type properly + //TODO: select route feature properly requestCreateSessionLocked(clientRecord.mClient, route, - route.getRouteTypes().get(0), uniqueRequestId); + route.getFeatures().get(0), uniqueRequestId); } } } @@ -653,7 +653,7 @@ class MediaRouter2ServiceImpl { } } - private List<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) { + private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -661,7 +661,7 @@ class MediaRouter2ServiceImpl { return Collections.emptyList(); } - List<RouteSessionInfo> sessionInfos = new ArrayList<>(); + List<RoutingSessionInfo> sessionInfos = new ArrayList<>(); for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) { sessionInfos.addAll(provider.getSessionInfos()); } @@ -742,7 +742,7 @@ class MediaRouter2ServiceImpl { public final boolean mTrusted; public final int mClientId; - public RouteDiscoveryRequest mDiscoveryRequest; + public RouteDiscoveryPreference mDiscoveryPreference; public boolean mIsManagerSelecting; public MediaRoute2Info mSelectingRoute; public MediaRoute2Info mSelectedRoute; @@ -752,7 +752,7 @@ class MediaRouter2ServiceImpl { mUserRecord = userRecord; mPackageName = packageName; mSelectRouteSequenceNumbers = new ArrayList<>(); - mDiscoveryRequest = RouteDiscoveryRequest.EMPTY; + mDiscoveryPreference = RouteDiscoveryPreference.EMPTY; mClient = client; mUid = uid; mPid = pid; @@ -876,20 +876,27 @@ class MediaRouter2ServiceImpl { @Override public void onSessionCreated(@NonNull MediaRoute2Provider provider, - @Nullable RouteSessionInfo sessionInfo, long requestId) { + @NonNull RoutingSessionInfo sessionInfo, long requestId) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler, this, provider, sessionInfo, requestId)); } @Override - public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo) { + public void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId) { + sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreationFailedOnHandler, + this, provider, requestId)); + } + + @Override + public void onSessionUpdated(@NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler, this, provider, sessionInfo)); } @Override - public void onSessionReleased(MediaRoute2Provider provider, RouteSessionInfo sessionInfo) { + public void onSessionReleased(MediaRoute2Provider provider, + RoutingSessionInfo sessionInfo) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler, this, provider, sessionInfo)); } @@ -932,7 +939,7 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "Ignoring invalid route : " + route); continue; } - MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId()); + MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId()); if (prevRoute != null) { if (!Objects.equals(prevRoute, route)) { @@ -978,7 +985,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionOnHandler(Client2Record clientRecord, - MediaRoute2Info route, String routeType, long requestId) { + MediaRoute2Info route, String routeFeature, long requestId) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { @@ -988,20 +995,20 @@ class MediaRouter2ServiceImpl { return; } - if (!route.getRouteTypes().contains(routeType)) { + if (!route.getFeatures().contains(routeFeature)) { Slog.w(TAG, "Ignoring session creation request since the given route=" + route - + " doesn't support the given type=" + routeType); + + " doesn't support the given feature=" + routeFeature); notifySessionCreationFailed(clientRecord, toClientRequestId(requestId)); return; } // TODO: Apply timeout for each request (How many seconds should we wait?) SessionCreationRequest request = new SessionCreationRequest( - clientRecord, route, routeType, requestId); + clientRecord, route, routeFeature, requestId); mSessionCreationRequests.add(request); provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(), - routeType, requestId); + routeFeature, requestId); } private void selectRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1131,7 +1138,14 @@ class MediaRouter2ServiceImpl { } private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider, - @Nullable RouteSessionInfo sessionInfo, long requestId) { + @NonNull RoutingSessionInfo sessionInfo, long requestId) { + + if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) { + // The session is created without any matching request. + // TODO: Tell managers for the session creation + return; + } + SessionCreationRequest matchingRequest = null; for (SessionCreationRequest request : mSessionCreationRequests) { @@ -1159,16 +1173,16 @@ class MediaRouter2ServiceImpl { } String originalRouteId = matchingRequest.mRoute.getId(); - String originalRouteType = matchingRequest.mRouteType; + String originalRouteFeature = matchingRequest.mRouteFeature; Client2Record client2Record = matchingRequest.mClientRecord; if (!sessionInfo.getSelectedRoutes().contains(originalRouteId) - || !TextUtils.equals(originalRouteType, - sessionInfo.getRouteType())) { + || !TextUtils.equals(originalRouteFeature, + sessionInfo.getRouteFeature())) { Slog.w(TAG, "Created session doesn't match the original request." + " originalRouteId=" + originalRouteId - + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId - + ", sessionInfo=" + sessionInfo); + + ", originalRouteFeature=" + originalRouteFeature + + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo); notifySessionCreationFailed(matchingRequest.mClientRecord, toClientRequestId(requestId)); return; @@ -1181,8 +1195,32 @@ class MediaRouter2ServiceImpl { // TODO: Tell managers for the session creation } + private void onSessionCreationFailedOnHandler(@NonNull MediaRoute2Provider provider, + long requestId) { + SessionCreationRequest matchingRequest = null; + + for (SessionCreationRequest request : mSessionCreationRequests) { + if (request.mRequestId == requestId + && TextUtils.equals( + request.mRoute.getProviderId(), provider.getUniqueId())) { + matchingRequest = request; + break; + } + } + + if (matchingRequest == null) { + Slog.w(TAG, "Ignoring session creation failed result for unknown request. " + + "requestId=" + requestId); + return; + } + + mSessionCreationRequests.remove(matchingRequest); + notifySessionCreationFailed(matchingRequest.mClientRecord, + toClientRequestId(requestId)); + } + private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo) { + @NonNull RoutingSessionInfo sessionInfo) { Client2Record client2Record = mSessionToClientMap.get( sessionInfo.getId()); @@ -1196,7 +1234,7 @@ class MediaRouter2ServiceImpl { } private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo) { + @NonNull RoutingSessionInfo sessionInfo) { Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId()); if (client2Record == null) { @@ -1208,8 +1246,8 @@ class MediaRouter2ServiceImpl { // TODO: Tell managers for the session release } - private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo, - int requestId) { + private void notifySessionCreated(Client2Record clientRecord, + RoutingSessionInfo sessionInfo, int requestId) { try { clientRecord.mClient.notifySessionCreated(sessionInfo, requestId); } catch (RemoteException ex) { @@ -1228,7 +1266,7 @@ class MediaRouter2ServiceImpl { } private void notifySessionInfoChanged(Client2Record clientRecord, - RouteSessionInfo sessionInfo) { + RoutingSessionInfo sessionInfo) { try { clientRecord.mClient.notifySessionInfoChanged(sessionInfo); } catch (RemoteException ex) { @@ -1238,7 +1276,7 @@ class MediaRouter2ServiceImpl { } private void notifySessionReleased(Client2Record clientRecord, - RouteSessionInfo sessionInfo) { + RoutingSessionInfo sessionInfo) { try { clientRecord.mClient.notifySessionReleased(sessionInfo); } catch (RemoteException ex) { @@ -1412,8 +1450,8 @@ class MediaRouter2ServiceImpl { try { manager.notifyRouteSelected(clientRecord.mPackageName, clientRecord.mSelectedRoute); - manager.notifyRouteTypesChanged(clientRecord.mPackageName, - clientRecord.mDiscoveryRequest.getRouteTypes()); + manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName, + clientRecord.mDiscoveryPreference.getPreferredFeatures()); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex); } @@ -1432,15 +1470,15 @@ class MediaRouter2ServiceImpl { final class SessionCreationRequest { public final Client2Record mClientRecord; public final MediaRoute2Info mRoute; - public final String mRouteType; + public final String mRouteFeature; public final long mRequestId; SessionCreationRequest(@NonNull Client2Record clientRecord, @NonNull MediaRoute2Info route, - @NonNull String routeType, long requestId) { + @NonNull String routeFeature, long requestId) { mClientRecord = clientRecord; mRoute = route; - mRouteType = routeType; + mRouteFeature = routeFeature; mRequestId = requestId; } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index c76555cf15cf..b7aa484a3fc7 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -39,8 +39,8 @@ import android.media.MediaRouter; import android.media.MediaRouterClientState; import android.media.RemoteDisplayState; import android.media.RemoteDisplayState.RemoteDisplayInfo; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -520,7 +520,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub } // Binder call @Override - public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryRequest request) { + public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) { mService2.setDiscoveryRequest2(client, request); } @@ -552,7 +552,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { + public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { return mService2.getActiveSessions(manager); } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 0ea4e63231d4..6695227fd236 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -30,12 +30,12 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; -import android.text.TextUtils; import android.util.Log; import com.android.internal.R; import java.util.Collections; +import java.util.List; /** * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers. @@ -55,6 +55,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { private final IAudioService mAudioService; private final Handler mHandler; private final Context mContext; + private final BluetoothRouteProvider mBtRouteProvider; private static ComponentName sComponentName = new ComponentName( SystemMediaRoute2Provider.class.getPackageName$(), @@ -62,7 +63,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { //TODO: Clean up these when audio manager support multiple bt devices MediaRoute2Info mDefaultRoute; - MediaRoute2Info mBluetoothA2dpRoute; + @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST; final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { @@ -87,6 +88,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); + mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> { + mBluetoothRoutes = routes; + publishRoutes(); + }); initializeRoutes(); } @@ -141,8 +146,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(TYPE_LIVE_AUDIO) - .addRouteType(TYPE_LIVE_VIDEO) + .addFeature(TYPE_LIVE_AUDIO) + .addFeature(TYPE_LIVE_VIDEO) .build(); AudioRoutesInfo newAudioRoutes = null; @@ -157,7 +162,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { updateAudioRoutes(newAudioRoutes); } - publishRoutes(); + mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes(); + + MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); + builder.addRoute(mDefaultRoute); + for (MediaRoute2Info route : mBluetoothRoutes) { + builder.addRoute(route); + } + setProviderState(builder.build()); + mHandler.post(() -> notifyProviderState()); } void updateAudioRoutes(AudioRoutesInfo newRoutes) { @@ -181,25 +194,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(TYPE_LIVE_AUDIO) - .addRouteType(TYPE_LIVE_VIDEO) + .addFeature(TYPE_LIVE_AUDIO) + .addFeature(TYPE_LIVE_VIDEO) .build(); - if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { - mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; - if (mCurAudioRoutesInfo.bluetoothName != null) { - //TODO: mark as bluetooth once MediaRoute2Info has device type - mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID, - mCurAudioRoutesInfo.bluetoothName) - .setDescription(mContext.getResources().getText( - R.string.bluetooth_a2dp_audio_route_name).toString()) - .addRouteType(TYPE_LIVE_AUDIO) - .build(); - } else { - mBluetoothA2dpRoute = null; - } - } - publishRoutes(); } @@ -207,15 +205,13 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { * The first route should be the currently selected system route. * For example, if there are two system routes (BT and device speaker), * BT will be the first route in the list. - * - * TODO: Support multiple BT devices */ void publishRoutes() { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); - if (mBluetoothA2dpRoute != null) { - builder.addRoute(mBluetoothA2dpRoute); - } builder.addRoute(mDefaultRoute); - setAndNotifyProviderState(builder.build(), Collections.emptyList()); + for (MediaRoute2Info route : mBluetoothRoutes) { + builder.addRoute(route); + } + setAndNotifyProviderState(builder.build()); } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 7f650ee7e138..b24a938ef7ea 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -18,8 +18,10 @@ package com.android.server.net; import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal; +import android.annotation.NonNull; import android.net.Network; import android.net.NetworkTemplate; +import android.net.netstats.provider.AbstractNetworkStatsProvider; import android.telephony.SubscriptionPlan; import java.util.Set; @@ -126,4 +128,12 @@ public abstract class NetworkPolicyManagerInternal { */ public abstract void setMeteredRestrictedPackagesAsync( Set<String> packageNames, int userId); + + /** + * Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota + * which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}. + * + * @param tag the human readable identifier of the custom network stats provider. + */ + public abstract void onStatsProviderLimitReached(@NonNull String tag); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 4a45730ccca5..d8a4655815ad 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -75,6 +75,7 @@ import static android.net.NetworkTemplate.MATCH_MOBILE; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.TrafficStats.MB_IN_BYTES; +import static android.net.netstats.provider.AbstractNetworkStatsProvider.QUOTA_UNLIMITED; import static android.os.Trace.TRACE_TAG_NETWORK; import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED; import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED; @@ -391,6 +392,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17; private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18; private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19; + private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20; private static final int UID_MSG_STATE_CHANGED = 100; private static final int UID_MSG_GONE = 101; @@ -4518,6 +4520,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mListeners.finishBroadcast(); return true; } + case MSG_STATS_PROVIDER_LIMIT_REACHED: { + mNetworkStats.forceUpdate(); + synchronized (mNetworkPoliciesSecondLock) { + updateNetworkEnabledNL(); + updateNotificationsNL(); + } + return true; + } case MSG_LIMIT_REACHED: { final String iface = (String) msg.obj; @@ -4573,14 +4583,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return true; } case MSG_UPDATE_INTERFACE_QUOTA: { - removeInterfaceQuota((String) msg.obj); + final String iface = (String) msg.obj; // int params need to be stitched back into a long - setInterfaceQuota((String) msg.obj, - ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL)); + final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL); + removeInterfaceQuota(iface); + setInterfaceQuota(iface, quota); + mNetworkStats.setStatsProviderLimit(iface, quota); return true; } case MSG_REMOVE_INTERFACE_QUOTA: { - removeInterfaceQuota((String) msg.obj); + final String iface = (String) msg.obj; + removeInterfaceQuota(iface); + mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED); return true; } case MSG_RESET_FIREWALL_RULES_BY_UID: { @@ -5235,6 +5249,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED, userId, 0, packageNames).sendToTarget(); } + + @Override + public void onStatsProviderLimitReached(@NonNull String tag) { + Log.v(TAG, "onStatsProviderLimitReached: " + tag); + mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget(); + } } private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) { diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java index 4843ede7219d..6d72cb5ee345 100644 --- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java @@ -16,6 +16,7 @@ package com.android.server.net; +import android.annotation.NonNull; import android.net.NetworkStats; import android.net.NetworkTemplate; @@ -34,4 +35,10 @@ public abstract class NetworkStatsManagerInternal { /** Force update of statistics. */ public abstract void forceUpdate(); + + /** + * Set the quota limit to all registered custom network stats providers. + * Note that invocation of any interface will be sent to all providers. + */ + public abstract void setStatsProviderLimit(@NonNull String iface, long quota); } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 7a6f29764f09..1dcff07e36a2 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -18,6 +18,7 @@ package com.android.server.net; import static android.Manifest.permission.ACCESS_NETWORK_STATE; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; +import static android.Manifest.permission.UPDATE_DEVICE_STATS; import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_REMOVED; @@ -71,6 +72,7 @@ import static com.android.server.NetworkManagementSocketTagger.resetKernelUidSta import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.usage.NetworkStatsManager; @@ -97,6 +99,9 @@ import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; +import android.net.netstats.provider.INetworkStatsProvider; +import android.net.netstats.provider.INetworkStatsProviderCallback; +import android.net.netstats.provider.NetworkStatsProviderCallback; import android.os.BestClock; import android.os.Binder; import android.os.DropBoxManager; @@ -109,6 +114,7 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -176,7 +182,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * This avoids firing the global alert too often on devices with high transfer speeds and * high quota. */ - private static final int PERFORM_POLL_DELAY_MS = 1000; + private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000; private static final String TAG_NETSTATS_ERROR = "netstats_error"; @@ -220,6 +226,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ public interface NetworkStatsSettings { public long getPollInterval(); + public long getPollDelay(); public boolean getSampleEnabled(); public boolean getAugmentEnabled(); @@ -248,6 +255,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } private final Object mStatsLock = new Object(); + private final Object mStatsProviderLock = new Object(); /** Set of currently active ifaces. */ @GuardedBy("mStatsLock") @@ -272,6 +280,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final DropBoxNonMonotonicObserver mNonMonotonicObserver = new DropBoxNonMonotonicObserver(); + private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList = + new RemoteCallbackList<>(); + @GuardedBy("mStatsLock") private NetworkStatsRecorder mDevRecorder; @GuardedBy("mStatsLock") @@ -502,9 +513,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Register for a global alert that is delivered through - * {@link INetworkManagementEventObserver} once a threshold amount of data - * has been transferred. + * Register for a global alert that is delivered through {@link INetworkManagementEventObserver} + * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has + * been transferred. */ private void registerGlobalAlert() { try { @@ -514,6 +525,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } catch (RemoteException e) { // ignored; service lives in system_server } + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes)); } @Override @@ -803,8 +815,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void incrementOperationCount(int uid, int tag, int operationCount) { if (Binder.getCallingUid() != uid) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.UPDATE_DEVICE_STATS, TAG); + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); } if (operationCount < 0) { @@ -1095,7 +1106,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * Observer that watches for {@link INetworkManagementService} alerts. */ - private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() { + private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() { @Override public void limitReached(String limitName, String iface) { // only someone like NMS should be calling us @@ -1106,7 +1117,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // such a call pending; UID stats are handled during normal polling interval. if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) { mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT, - PERFORM_POLL_DELAY_MS); + mSettings.getPollDelay()); } } } @@ -1252,6 +1263,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { xtSnapshot.combineAllValues(tetherSnapshot); devSnapshot.combineAllValues(tetherSnapshot); + // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data + // from stats providers that isn't already counted by dev and XT stats. + Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider"); + final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE); + Trace.traceEnd(TRACE_TAG_NETWORK); + xtSnapshot.combineAllValues(providersnapshot); + devSnapshot.combineAllValues(providersnapshot); + // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic // can't be reattributed to responsible apps. Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev"); @@ -1355,6 +1374,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { performSampleLocked(); } + // request asynchronous stats update from all providers for next poll. + // TODO: request with a valid token. + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */)); + // finally, dispatch updated event to any listeners final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED); updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); @@ -1476,6 +1499,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public void forceUpdate() { NetworkStatsService.this.forceUpdate(); } + + @Override + public void setStatsProviderLimit(@NonNull String iface, long quota) { + Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")"); + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota)); + } } @Override @@ -1690,6 +1719,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { uidSnapshot.combineAllValues(vtStats); } + // get a stale copy of uid stats snapshot provided by providers. + final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID); + providerStats.filter(UID_ALL, ifaces, TAG_ALL); + mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats, mUseBpfTrafficStats); + uidSnapshot.combineAllValues(providerStats); + uidSnapshot.combineAllValues(mUidOperations); return uidSnapshot; @@ -1726,6 +1761,152 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + /** + * Registers a custom provider of {@link android.net.NetworkStats} to combine the network + * statistics that cannot be seen by the kernel to system. To unregister, invoke the + * {@code unregister()} of the returned callback. + * + * @param tag a human readable identifier of the custom network stats provider. + * @param provider the binder interface of + * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that + * needs to be registered to the system. + * + * @return a binder interface of + * {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be + * used to report events to the system. + */ + public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider( + @NonNull String tag, @NonNull INetworkStatsProvider provider) { + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + Objects.requireNonNull(provider, "provider is null"); + Objects.requireNonNull(tag, "tag is null"); + try { + NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl( + tag, provider, mAlertObserver, mStatsProviderCbList); + mStatsProviderCbList.register(callback); + Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid=" + + getCallingUid() + "/" + getCallingPid()); + return callback; + } catch (RemoteException e) { + Log.e(TAG, "registerNetworkStatsProvider failed", e); + } + return null; + } + + // Collect stats from local cache of providers. + private @NonNull NetworkStats getNetworkStatsFromProviders(int how) { + final NetworkStats ret = new NetworkStats(0L, 0); + invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how))); + return ret; + } + + @FunctionalInterface + private interface ThrowingConsumer<S, T extends Throwable> { + void accept(S s) throws T; + } + + private void invokeForAllStatsProviderCallbacks( + @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) { + synchronized (mStatsProviderCbList) { + final int length = mStatsProviderCbList.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + final NetworkStatsProviderCallbackImpl cb = + mStatsProviderCbList.getBroadcastItem(i); + try { + task.accept(cb); + } catch (RemoteException e) { + Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e); + } + } + } finally { + mStatsProviderCbList.finishBroadcast(); + } + } + } + + private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub + implements IBinder.DeathRecipient { + @NonNull final String mTag; + @NonNull private final Object mProviderStatsLock = new Object(); + @NonNull final INetworkStatsProvider mProvider; + @NonNull final INetworkManagementEventObserver mAlertObserver; + @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList; + + @GuardedBy("mProviderStatsLock") + // STATS_PER_IFACE and STATS_PER_UID + private final NetworkStats mIfaceStats = new NetworkStats(0L, 0); + @GuardedBy("mProviderStatsLock") + private final NetworkStats mUidStats = new NetworkStats(0L, 0); + + NetworkStatsProviderCallbackImpl( + @NonNull String tag, @NonNull INetworkStatsProvider provider, + @NonNull INetworkManagementEventObserver alertObserver, + @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList) + throws RemoteException { + mTag = tag; + mProvider = provider; + mProvider.asBinder().linkToDeath(this, 0); + mAlertObserver = alertObserver; + mStatsProviderCbList = cbList; + } + + @NonNull + public NetworkStats getCachedStats(int how) { + synchronized (mProviderStatsLock) { + NetworkStats stats; + switch (how) { + case STATS_PER_IFACE: + stats = mIfaceStats; + break; + case STATS_PER_UID: + stats = mUidStats; + break; + default: + throw new IllegalArgumentException("Invalid type: " + how); + } + // Return a defensive copy instead of local reference. + return stats.clone(); + } + } + + @Override + public void onStatsUpdated(int token, @Nullable NetworkStats ifaceStats, + @Nullable NetworkStats uidStats) { + // TODO: 1. Use token to map ifaces to correct NetworkIdentity. + // 2. Store the difference and store it directly to the recorder. + synchronized (mProviderStatsLock) { + if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats); + if (uidStats != null) mUidStats.combineAllValues(uidStats); + } + } + + @Override + public void onAlertReached() throws RemoteException { + mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */); + } + + @Override + public void onLimitReached() { + Log.d(TAG, mTag + ": onLimitReached"); + LocalServices.getService(NetworkPolicyManagerInternal.class) + .onStatsProviderLimitReached(mTag); + } + + @Override + public void binderDied() { + Log.d(TAG, mTag + ": binderDied"); + mStatsProviderCbList.unregister(this); + } + + @Override + public void unregister() { + Log.d(TAG, mTag + ": unregister"); + mStatsProviderCbList.unregister(this); + } + + } + @VisibleForTesting static class HandlerCallback implements Handler.Callback { private final NetworkStatsService mService; @@ -1815,6 +1996,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); } @Override + public long getPollDelay() { + return DEFAULT_PERFORM_POLL_DELAY_MS; + } + @Override public long getGlobalAlertBytes(long def) { return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def); } diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index d66fd574ef4a..da2ca9b0cfde 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -43,7 +43,8 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor } record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(), - record.sbn.getUid(), record.getChannel().getId(), false)); + record.sbn.getUid(), record.getChannel().getId(), + record.getNotification().getShortcutId(), false)); return null; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 728b297b4d8e..0e08033e0f68 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -25,6 +25,7 @@ import static android.app.Notification.FLAG_INSISTENT; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; @@ -236,6 +237,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; import com.android.server.DeviceIdleInternal; @@ -1008,12 +1010,14 @@ public class NotificationManagerService extends SystemService { if (clearEffects) { clearEffects(); } + mAssistants.onPanelRevealed(items); } @Override public void onPanelHidden() { MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL); EventLogTags.writeNotificationPanelHidden(); + mAssistants.onPanelHidden(); } @Override @@ -1060,6 +1064,7 @@ public class NotificationManagerService extends SystemService { reportSeen(r); } r.setVisibility(true, nv.rank, nv.count); + mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true); boolean isHun = (nv.location == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); // hasBeenVisiblyExpanded must be called after updating the expansion state of @@ -1078,6 +1083,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(nv.key); if (r == null) continue; r.setVisibility(false, nv.rank, nv.count); + mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false); nv.recycle(); } } @@ -2219,8 +2225,8 @@ public class NotificationManagerService extends SystemService { maybeNotifyChannelOwner(pkg, uid, preUpdate, channel); if (!fromListener) { - final NotificationChannel modifiedChannel = - mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false); + final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel( + pkg, uid, channel.getId(), false); mListeners.notifyNotificationChannelChanged( pkg, UserHandle.getUserHandleForUid(uid), modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); @@ -3017,21 +3023,43 @@ public class NotificationManagerService extends SystemService { @Override public void createNotificationChannels(String pkg, - ParceledListSlice channelsList) throws RemoteException { + ParceledListSlice channelsList) { checkCallerIsSystemOrSameApp(pkg); createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList); } @Override public void createNotificationChannelsForPackage(String pkg, int uid, - ParceledListSlice channelsList) throws RemoteException { - checkCallerIsSystem(); + ParceledListSlice channelsList) { + enforceSystemOrSystemUI("only system can call this"); createNotificationChannelsImpl(pkg, uid, channelsList); } @Override + public void createConversationNotificationChannelForPackage(String pkg, int uid, + NotificationChannel parentChannel, String conversationId) { + enforceSystemOrSystemUI("only system can call this"); + Preconditions.checkNotNull(parentChannel); + Preconditions.checkNotNull(conversationId); + String parentId = parentChannel.getId(); + NotificationChannel conversationChannel = parentChannel; + conversationChannel.setId(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId)); + conversationChannel.setConversationId(parentId, conversationId); + createNotificationChannelsImpl( + pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel))); + } + + @Override public NotificationChannel getNotificationChannel(String callingPkg, int userId, String targetPkg, String channelId) { + return getConversationNotificationChannel( + callingPkg, userId, targetPkg, channelId, null); + } + + @Override + public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, + String targetPkg, String channelId, String conversationId) { if (canNotifyAsPackage(callingPkg, targetPkg, userId) || isCallingUidSystem()) { int targetUid = -1; @@ -3041,7 +3069,8 @@ public class NotificationManagerService extends SystemService { /* ignore */ } return mPreferencesHelper.getNotificationChannel( - targetPkg, targetUid, channelId, false /* includeDeleted */); + targetPkg, targetUid, channelId, conversationId, + false /* includeDeleted */); } throw new SecurityException("Pkg " + callingPkg + " cannot read channels for " + targetPkg + " in " + userId); @@ -3072,6 +3101,30 @@ public class NotificationManagerService extends SystemService { } @Override + public void deleteConversationNotificationChannels(String pkg, int uid, + String conversationId) { + checkCallerIsSystem(); + final int callingUid = Binder.getCallingUid(); + List<NotificationChannel> channels = + mPreferencesHelper.getNotificationChannelsByConversationId( + pkg, uid, conversationId); + if (!channels.isEmpty()) { + for (NotificationChannel nc : channels) { + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true, + UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null); + mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId()); + mListeners.notifyNotificationChannelChanged(pkg, + UserHandle.getUserHandleForUid(callingUid), + mPreferencesHelper.getNotificationChannel( + pkg, callingUid, nc.getId(), true), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + } + handleSavePolicyFile(); + } + } + + + @Override public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getNotificationChannelGroupWithChannels( @@ -5203,7 +5256,8 @@ public class NotificationManagerService extends SystemService { channelId = (new Notification.TvExtender(notification)).getChannelId(); } final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg, - notificationUid, channelId, false /* includeDeleted */); + notificationUid, channelId, notification.getShortcutId(), + false /* includeDeleted */); if (channel == null) { final String noChannelStr = "No Channel found for " + "pkg=" + pkg @@ -8296,6 +8350,32 @@ public class NotificationManagerService extends SystemService { } } + protected void onPanelRevealed(int items) { + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onPanelRevealed(items); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex); + } + }); + } + } + + protected void onPanelHidden() { + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onPanelHidden(); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex); + } + }); + } + } + boolean hasUserSet(int userId) { synchronized (mLock) { return mUserSetMap.getOrDefault(userId, false); @@ -8363,6 +8443,24 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") + void notifyAssistantVisibilityChangedLocked( + final StatusBarNotification sbn, + final boolean isVisible) { + final String key = sbn.getKey(); + Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key); + notifyAssistantLocked( + sbn, + false /* sameUserOnly */, + (assistant, sbnHolder) -> { + try { + assistant.onNotificationVisibilityChanged(key, isVisible); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex); + } + }); + } + + @GuardedBy("mNotificationLock") void notifyAssistantExpansionChangedLocked( final StatusBarNotification sbn, final boolean isUserAction, diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index cdb0a17ada0a..92fcb7f99f9c 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -770,6 +770,13 @@ public class PreferencesHelper implements RankingConfig { channel.setShowBadge(false); } channel.setOriginalImportance(channel.getImportance()); + + // validate parent + if (channel.getParentChannelId() != null) { + Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()), + "Tried to create a conversation channel without a preexisting parent"); + } + r.channels.put(channel.getId(), channel); if (channel.canBypassDnd() != mAreChannelsBypassingDnd) { updateChannelsBypassingDnd(mContext.getUserId()); @@ -851,6 +858,13 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted) { Objects.requireNonNull(pkg); + return getNotificationChannel(pkg, uid, channelId, null, includeDeleted); + } + + @Override + public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + String conversationId, boolean includeDeleted) { + Preconditions.checkNotNull(pkg); synchronized (mPackagePreferences) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { @@ -859,14 +873,51 @@ public class PreferencesHelper implements RankingConfig { if (channelId == null) { channelId = NotificationChannel.DEFAULT_CHANNEL_ID; } - final NotificationChannel nc = r.channels.get(channelId); - if (nc != null && (includeDeleted || !nc.isDeleted())) { - return nc; + if (conversationId == null) { + final NotificationChannel nc = r.channels.get(channelId); + if (nc != null && (includeDeleted || !nc.isDeleted())) { + return nc; + } + } else { + // look for an automatically created conversation specific channel + return findConversationChannel(r, channelId, conversationId, includeDeleted); } return null; } } + private NotificationChannel findConversationChannel(PackagePreferences p, String parentId, + String conversationId, boolean includeDeleted) { + for (NotificationChannel nc : p.channels.values()) { + if (conversationId.equals(nc.getConversationId()) + && parentId.equals(nc.getParentChannelId()) + && (includeDeleted || !nc.isDeleted())) { + return nc; + } + } + return null; + } + + public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid, + String conversationId) { + Preconditions.checkNotNull(pkg); + Preconditions.checkNotNull(conversationId); + List<NotificationChannel> channels = new ArrayList<>(); + synchronized (mPackagePreferences) { + PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); + if (r == null) { + return channels; + } + for (NotificationChannel nc : r.channels.values()) { + if (conversationId.equals(nc.getConversationId()) + && !nc.isDeleted()) { + channels.add(nc); + } + } + return channels; + } + } + @Override public void deleteNotificationChannel(String pkg, int uid, String channelId) { synchronized (mPackagePreferences) { diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 7816f3619023..4b044c13ecc6 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -41,10 +41,15 @@ public interface RankingConfig { int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty); boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess); - void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); - NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted); + void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, + boolean fromUser); + NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + boolean includeDeleted); + NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + String conversationId, boolean includeDeleted); void deleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannels(String pkg, int uid); - ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted); + ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, + boolean includeDeleted); } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index b782ca96ae88..3c31f6a7f0d7 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -83,6 +83,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -925,7 +926,7 @@ public final class OverlayManagerService extends SystemService { /** * Updates the target packages' set of enabled overlays in PackageManager. */ - private void updateOverlayPaths(int userId, List<String> targetPackageNames) { + private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) { try { traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames); if (DEBUG) { @@ -955,6 +956,7 @@ public final class OverlayManagerService extends SystemService { } } + final HashSet<String> updatedPackages = new HashSet<>(); final int n = targetPackageNames.size(); for (int i = 0; i < n; i++) { final String targetPackageName = targetPackageNames.get(i); @@ -965,11 +967,13 @@ public final class OverlayManagerService extends SystemService { } if (!pm.setEnabledOverlayPackages( - userId, targetPackageName, pendingChanges.get(targetPackageName))) { + userId, targetPackageName, pendingChanges.get(targetPackageName), + updatedPackages)) { Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d", targetPackageName, userId)); } } + return new ArrayList<>(updatedPackages); } finally { traceEnd(TRACE_TAG_RRO); } @@ -980,10 +984,10 @@ public final class OverlayManagerService extends SystemService { } private void updateAssets(final int userId, List<String> targetPackageNames) { - updateOverlayPaths(userId, targetPackageNames); final IActivityManager am = ActivityManager.getService(); try { - am.scheduleApplicationInfoChanged(targetPackageNames, userId); + final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames); + am.scheduleApplicationInfoChanged(updatedPaths, userId); } catch (RemoteException e) { // Intentionally left empty. } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 019c9528f8ab..9623542a2900 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -685,7 +685,7 @@ final class OverlayManagerServiceImpl { // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers. if (targetPackage != null && overlayPackage != null && !("android".equals(targetPackageName) - && overlayPackage.isStaticOverlayPackage())) { + && overlayPackage.isStaticOverlayPackage())) { mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } @@ -703,9 +703,9 @@ final class OverlayManagerServiceImpl { if (currentState != newState) { if (DEBUG) { Slog.d(TAG, String.format("%s:%d: %s -> %s", - overlayPackageName, userId, - OverlayInfo.stateToString(currentState), - OverlayInfo.stateToString(newState))); + overlayPackageName, userId, + OverlayInfo.stateToString(currentState), + OverlayInfo.stateToString(newState))); } modified |= mSettings.setState(overlayPackageName, userId, newState); } diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING new file mode 100644 index 000000000000..52163a09e387 --- /dev/null +++ b/services/core/java/com/android/server/om/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.om." + } + ] + } + ] +} diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 307a07bb09a2..28079099469a 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -32,10 +32,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.parsing.AndroidPackage; import android.os.Environment; import android.os.RemoteException; import android.os.ServiceManager; import android.sysprop.ApexProperties; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Singleton; import android.util.Slog; @@ -44,15 +47,19 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.google.android.collect.Lists; + import java.io.File; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -97,12 +104,27 @@ abstract class ApexManager { * Minimal information about APEX mount points and the original APEX package they refer to. */ static class ActiveApexInfo { + @Nullable public final String apexModuleName; public final File apexDirectory; - public final File preinstalledApexPath; + public final File preInstalledApexPath; + + private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) { + this(null, apexDirectory, preInstalledApexPath); + } - private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) { + private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory, + File preInstalledApexPath) { + this.apexModuleName = apexModuleName; this.apexDirectory = apexDirectory; - this.preinstalledApexPath = preinstalledApexPath; + this.preInstalledApexPath = preInstalledApexPath; + } + + private ActiveApexInfo(ApexInfo apexInfo) { + this( + apexInfo.moduleName, + new File(Environment.getApexDirectory() + File.separator + + apexInfo.moduleName), + new File(apexInfo.preinstalledModulePath)); } } @@ -232,6 +254,17 @@ abstract class ApexManager { abstract boolean uninstallApex(String apexPackagePath); /** + * Registers an APK package as an embedded apk of apex. + */ + abstract void registerApkInApex(AndroidPackage pkg); + + /** + * Returns list of {@code packageName} of apks inside the given apex. + * @param apexPackageName Package name of the apk container of apex + */ + abstract List<String> getApksInApex(String apexPackageName); + + /** * Dumps various state information to the provided {@link PrintWriter} object. * * @param pw the {@link PrintWriter} object to send information to. @@ -255,16 +288,33 @@ abstract class ApexManager { static class ApexManagerImpl extends ApexManager { private final IApexService mApexService; private final Object mLock = new Object(); + + @GuardedBy("mLock") + private Set<ActiveApexInfo> mActiveApexInfosCache; + /** - * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code - * AndroidManifest.xml} - * - * <p>Note that key of this map is {@code packageName} field of the corresponding {@code - * AndroidManifest.xml}. - */ + * Contains the list of {@code packageName}s of apks-in-apex for given + * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the + * difference between {@code packageName} and {@code apexModuleName}. + */ + @GuardedBy("mLock") + private Map<String, List<String>> mApksInApex = new ArrayMap<>(); + @GuardedBy("mLock") private List<PackageInfo> mAllPackagesCache; + /** + * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The + * apk container has a reference name, called {@code packageName}, which is found inside the + * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference + * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file. + * + * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of + * the apk container to {@code apexModuleName} of the apex-payload inside. + */ + @GuardedBy("mLock") + private Map<String, String> mPackageNameToApexModuleName; + ApexManagerImpl(IApexService apexService) { mApexService = apexService; } @@ -291,18 +341,25 @@ abstract class ApexManager { @Override List<ActiveApexInfo> getActiveApexInfos() { - try { - return Arrays.stream(mApexService.getActivePackages()) - .map(apexInfo -> new ActiveApexInfo( - new File( - Environment.getApexDirectory() + File.separator - + apexInfo.moduleName), - new File(apexInfo.preinstalledModulePath))).collect( - Collectors.toList()); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to retrieve packages from apexservice", e); + synchronized (mLock) { + if (mActiveApexInfosCache == null) { + try { + mActiveApexInfosCache = new ArraySet<>(); + final ApexInfo[] activePackages = mApexService.getActivePackages(); + for (int i = 0; i < activePackages.length; i++) { + ApexInfo apexInfo = activePackages[i]; + mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo)); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to retrieve packages from apexservice", e); + } + } + if (mActiveApexInfosCache != null) { + return new ArrayList<>(mActiveApexInfosCache); + } else { + return Collections.emptyList(); + } } - return Collections.emptyList(); } @Override @@ -318,6 +375,33 @@ abstract class ApexManager { }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); } + private void populatePackageNameToApexModuleNameIfNeeded() { + synchronized (mLock) { + if (mPackageNameToApexModuleName != null) { + return; + } + try { + mPackageNameToApexModuleName = new ArrayMap<>(); + final ApexInfo[] allPkgs = mApexService.getAllPackages(); + for (int i = 0; i < allPkgs.length; i++) { + ApexInfo ai = allPkgs[i]; + PackageParser.PackageLite pkgLite; + try { + File apexFile = new File(ai.modulePath); + pkgLite = PackageParser.parsePackageLite(apexFile, 0); + } catch (PackageParser.PackageParserException pe) { + throw new IllegalStateException("Unable to parse: " + + ai.modulePath, pe); + } + mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName); + } + } catch (RemoteException re) { + Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re); + throw new RuntimeException(re); + } + } + } + private void populateAllPackagesCacheIfNeeded() { synchronized (mLock) { if (mAllPackagesCache != null) { @@ -366,7 +450,6 @@ abstract class ApexManager { } factoryPackagesSet.add(packageInfo.packageName); } - } } catch (RemoteException re) { Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); @@ -533,6 +616,36 @@ abstract class ApexManager { } } + @Override + void registerApkInApex(AndroidPackage pkg) { + synchronized (mLock) { + final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator(); + while (it.hasNext()) { + final ActiveApexInfo aai = it.next(); + if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) { + List<String> apks = mApksInApex.get(aai.apexModuleName); + if (apks == null) { + apks = Lists.newArrayList(); + mApksInApex.put(aai.apexModuleName, apks); + } + apks.add(pkg.getPackageName()); + } + } + } + } + + @Override + List<String> getApksInApex(String apexPackageName) { + populatePackageNameToApexModuleNameIfNeeded(); + synchronized (mLock) { + String moduleName = mPackageNameToApexModuleName.get(apexPackageName); + if (moduleName == null) { + return Collections.emptyList(); + } + return mApksInApex.getOrDefault(moduleName, Collections.emptyList()); + } + } + /** * Dump information about the packages contained in a particular cache * @param packagesCache the cache to print information about. @@ -614,7 +727,6 @@ abstract class ApexManager { * updating APEX packages. */ private static final class ApexManagerFlattenedApex extends ApexManager { - @Override List<ActiveApexInfo> getActiveApexInfos() { // There is no apexd running in case of flattened apex @@ -721,6 +833,16 @@ abstract class ApexManager { } @Override + void registerApkInApex(AndroidPackage pkg) { + // No-op + } + + @Override + List<String> getApksInApex(String apexPackageName) { + return Collections.emptyList(); + } + + @Override void dump(PrintWriter pw, String packageName) { // No-op } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index c4bcf809a67d..3e760962da87 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -449,6 +449,7 @@ public class AppsFilter { } final PackageSetting callingPkgSetting; final ArraySet<PackageSetting> callingSharedPkgSettings; + Trace.beginSection("callingSetting instanceof"); if (callingSetting instanceof PackageSetting) { callingPkgSetting = (PackageSetting) callingSetting; callingSharedPkgSettings = null; @@ -456,6 +457,7 @@ public class AppsFilter { callingPkgSetting = null; callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages; } + Trace.endSection(); if (callingPkgSetting != null) { if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) { @@ -485,6 +487,7 @@ public class AppsFilter { return true; } final String targetName = targetPkg.getPackageName(); + Trace.beginSection("getAppId"); final int callingAppId; if (callingPkgSetting != null) { callingAppId = callingPkgSetting.appId; @@ -492,6 +495,7 @@ public class AppsFilter { callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same } final int targetAppId = targetPkgSetting.appId; + Trace.endSection(); if (callingAppId == targetAppId) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "same app id"); @@ -499,38 +503,64 @@ public class AppsFilter { return false; } - if (callingSetting.getPermissionsState().hasPermission( - Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "has query-all permission"); + try { + Trace.beginSection("hasPermission"); + if (callingSetting.getPermissionsState().hasPermission( + Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "has query-all permission"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - if (mForceQueryable.contains(targetAppId)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "force queryable"); + try { + Trace.beginSection("mForceQueryable"); + if (mForceQueryable.contains(targetAppId)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "force queryable"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { - // the calling package has explicitly declared the target package; allow - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries package"); + try { + Trace.beginSection("mQueriesViaPackage"); + if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { + // the calling package has explicitly declared the target package; allow + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "queries package"); + } + return false; } - return false; - } else if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries intent"); + } finally { + Trace.endSection(); + } + try { + Trace.beginSection("mQueriesViaIntent"); + if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "queries intent"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - final int targetUid = UserHandle.getUid(userId, targetAppId); - if (mImplicitlyQueryable.contains(callingUid, targetUid)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "implicitly queryable for user"); + try { + Trace.beginSection("mImplicitlyQueryable"); + final int targetUid = UserHandle.getUid(userId, targetAppId); + if (mImplicitlyQueryable.contains(callingUid, targetUid)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "implicitly queryable for user"); + } + return false; } - return false; + } finally { + Trace.endSection(); } if (callingPkgSetting != null) { if (callingPkgInstruments(callingPkgSetting, targetPkgSetting, targetName)) { @@ -576,17 +606,22 @@ public class AppsFilter { private static boolean callingPkgInstruments(PackageSetting callingPkgSetting, PackageSetting targetPkgSetting, String targetName) { - final List<ComponentParseUtils.ParsedInstrumentation> inst = - callingPkgSetting.pkg.getInstrumentations(); - for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) { - if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) { - if (DEBUG_LOGGING) { - log(callingPkgSetting, targetPkgSetting, "instrumentation"); + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingPkgInstruments"); + final List<ComponentParseUtils.ParsedInstrumentation> inst = + callingPkgSetting.pkg.getInstrumentations(); + for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) { + if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) { + if (DEBUG_LOGGING) { + log(callingPkgSetting, targetPkgSetting, "instrumentation"); + } + return true; } - return true; } + return false; + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - return false; } private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting, diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index ffcd6cf8603c..bcfe5773cfa4 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -338,9 +338,8 @@ class InstantAppRegistry { } @GuardedBy("mService.mLock") - public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, + public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, @Nullable PackageSetting ps, @NonNull int[] userIds) { - PackageSetting ps = mService.getPackageSetting(pkg.getPackageName()); if (ps == null) { return; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index c43f23454ca6..6331dd46c035 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -188,7 +188,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } }; - public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) { + public PackageInstallerService(Context context, PackageManagerService pm) { mContext = context; mPm = pm; mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class); @@ -206,9 +206,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions"); mSessionsDir.mkdirs(); - mApexManager = am; - - mStagingManager = new StagingManager(this, am, context); + mApexManager = ApexManager.getInstance(); + mStagingManager = new StagingManager(this, context); } boolean okToSendBroadcasts() { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 97a2d43e6183..165bdebe070f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2413,16 +2413,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void addFile(String name, long lengthBytes, byte[] metadata) { - if (mIncrementalFileStorages != null) { - try { - mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata)); - //TODO(b/136132412): merge incremental and callback installation schemes - return; - } catch (IOException ex) { - throw new IllegalStateException( - "Failed to add and configure Incremental File: " + name, ex); - } - } if (!isDataLoaderInstallation()) { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); @@ -2435,7 +2425,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("addFile"); - mFiles.add(FileInfo.added(name, lengthBytes, metadata)); } } @@ -2486,7 +2475,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ private void prepareDataLoader() throws PackageManagerException, StreamingException { - if (!isStreamingInstallation()) { + if (!isDataLoaderInstallation()) { return; } @@ -2500,6 +2489,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { file -> file.name.substring( 0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect( Collectors.toList()); + if (mIncrementalFileStorages != null) { + for (InstallationFile file : addedFiles) { + try { + mIncrementalFileStorages.addFile(file); + } catch (IOException ex) { + // TODO(b/146080380): add incremental-specific error code + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Failed to add and configure Incremental File: " + file.getName(), ex); + } + } + return; + } final FileSystemConnector connector = new FileSystemConnector(addedFiles); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b26e6c7021c1..159b4e49bfea 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -492,6 +492,7 @@ public class PackageManagerService extends IPackageManager.Stub static final int SCAN_AS_PRODUCT = 1 << 20; static final int SCAN_AS_SYSTEM_EXT = 1 << 21; static final int SCAN_AS_ODM = 1 << 22; + static final int SCAN_AS_APK_IN_APEX = 1 << 23; @IntDef(flag = true, prefix = { "SCAN_" }, value = { SCAN_NO_DEX, @@ -2589,6 +2590,9 @@ public class PackageManagerService extends IPackageManager.Stub & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) { return true; } + if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { + return true; + } return false; } @@ -3332,7 +3336,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - mInstallerService = new PackageInstallerService(mContext, this, mApexManager); + mInstallerService = new PackageInstallerService(mContext, this); final Pair<ComponentName, String> instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { @@ -5344,8 +5348,9 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForComponent(flags, userId); - mPermissionManager.enforceCrossUserPermission(callingUid, userId, - false /* requireFullPermission */, false /* checkShell */, "get service info"); + mPermissionManager.enforceCrossUserOrProfilePermission( + callingUid, userId, false /* requireFullPermission */, false /* checkShell */, + "get service info"); synchronized (mLock) { ParsedService s = mComponentResolver.getService(component); if (DEBUG_PACKAGE_INFO) Log.v( @@ -7795,8 +7800,10 @@ public class PackageManagerService extends IPackageManager.Stub String resolvedType, int flags, int userId, int callingUid, boolean includeInstantApps) { if (!mUserManager.exists(userId)) return Collections.emptyList(); - mPermissionManager.enforceCrossUserPermission(callingUid, userId, - false /*requireFullPermission*/, false /*checkShell*/, + mPermissionManager.enforceCrossUserOrProfilePermission(callingUid, + userId, + false /*requireFullPermission*/, + false /*checkShell*/, "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps); @@ -11595,6 +11602,23 @@ public class PackageManagerService extends IPackageManager.Stub return false; } SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx); + + // Remove the shared library overlays from its dependent packages. + for (int currentUserId : UserManagerService.getInstance().getUserIds()) { + final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr( + libraryInfo, 0, currentUserId); + if (dependents == null) { + continue; + } + for (VersionedPackage dependentPackage : dependents) { + final PackageSetting ps = mSettings.mPackages.get( + dependentPackage.getPackageName()); + if (ps != null) { + ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId); + } + } + } + versionedLib.remove(version); if (versionedLib.size() <= 0) { mSharedLibraries.remove(name); @@ -11710,6 +11734,9 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.insertPackageSettingLPw(pkgSetting, pkg); // Add the new setting to mPackages mPackages.put(pkg.getAppInfoPackageName(), pkg); + if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { + mApexManager.registerApkInApex(pkg); + } // Add the package's KeySets to the global KeySetManagerService KeySetManagerService ksms = mSettings.mKeySetManagerService; @@ -15200,6 +15227,29 @@ public class PackageManagerService extends IPackageManager.Stub // upcoming call to mSettings.writeLPr(). } } + + // Retrieve the overlays for shared libraries of the package. + if (pkg.getUsesLibraryInfos() != null) { + for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) { + for (int currentUserId : UserManagerService.getInstance().getUserIds()) { + if (!sharedLib.isDynamic()) { + // TODO(146804378): Support overlaying static shared libraries + continue; + } + final PackageSetting libPs = mSettings.mPackages.get( + sharedLib.getPackageName()); + if (libPs == null) { + continue; + } + final String[] overlayPaths = libPs.getOverlayPaths(currentUserId); + if (overlayPaths != null) { + ps.setOverlayPathsForLibrary(sharedLib.getName(), + Arrays.asList(overlayPaths), currentUserId); + } + } + } + } + // It's implied that when a user requests installation, they want the app to be // installed and enabled. if (userId != UserHandle.USER_ALL) { @@ -17452,7 +17502,8 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mLock) { if (res) { if (pkg != null) { - mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers); + mInstantAppRegistry.onPackageUninstalledLPw(pkg, uninstalledPs, + info.removedUsers); } updateSequenceNumberLP(uninstalledPs, info.removedUsers); updateInstantAppInstallerLocked(packageName); @@ -17757,10 +17808,10 @@ public class PackageManagerService extends IPackageManager.Stub ApexManager.ActiveApexInfo apexInfo) { for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) { SystemPartition sp = SYSTEM_PARTITIONS.get(i); - if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith( + if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith( sp.folder.getAbsolutePath())) { - return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag, - false /* hasOverlays */); + return new SystemPartition(apexInfo.apexDirectory, + sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */); } } return null; @@ -20122,8 +20173,7 @@ public class PackageManagerService extends IPackageManager.Stub // Disable any carrier apps. We do this very early in boot to prevent the apps from being // disabled after already being started. CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this, - mPermissionManagerService, mContext.getContentResolver(), - UserHandle.USER_SYSTEM); + mPermissionManagerService, UserHandle.USER_SYSTEM, mContext); disableSkuSpecificApps(); @@ -22791,7 +22841,7 @@ public class PackageManagerService extends IPackageManager.Stub ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length); for (String pkgName: pkgNames) { - synchronized (mPackages) { + synchronized (mLock) { if (pkgName == null) { continue; } @@ -23240,9 +23290,11 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName, - @Nullable List<String> overlayPackageNames) { + @Nullable List<String> overlayPackageNames, + @NonNull Collection<String> outUpdatedPackageNames) { synchronized (mLock) { - if (targetPackageName == null || mPackages.get(targetPackageName) == null) { + final AndroidPackage targetPkg = mPackages.get(targetPackageName); + if (targetPackageName == null || targetPkg == null) { Slog.e(TAG, "failed to find package " + targetPackageName); return false; } @@ -23261,8 +23313,41 @@ public class PackageManagerService extends IPackageManager.Stub } } + ArraySet<String> updatedPackageNames = null; + if (targetPkg.getLibraryNames() != null) { + // Set the overlay paths for dependencies of the shared library. + updatedPackageNames = new ArraySet<>(); + for (String libName : targetPkg.getLibraryNames()) { + final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName, + SharedLibraryInfo.VERSION_UNDEFINED); + if (info == null) { + continue; + } + final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr( + info, 0, userId); + if (dependents == null) { + continue; + } + for (VersionedPackage dependent : dependents) { + final PackageSetting ps = mSettings.mPackages.get( + dependent.getPackageName()); + if (ps == null) { + continue; + } + ps.setOverlayPathsForLibrary(libName, overlayPaths, userId); + updatedPackageNames.add(dependent.getPackageName()); + } + } + } + final PackageSetting ps = mSettings.mPackages.get(targetPackageName); ps.setOverlayPaths(overlayPaths, userId); + + outUpdatedPackageNames.add(targetPackageName); + if (updatedPackageNames != null) { + outUpdatedPackageNames.addAll(updatedPackageNames); + } + return true; } } @@ -23452,6 +23537,11 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public List<String> getApksInApex(String apexPackageName) { + return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName); + } + + @Override public void uninstallApex(String packageName, long versionCode, int userId, IntentSender intentSender, int flags) { final int callerUid = Binder.getCallingUid(); @@ -23632,7 +23722,7 @@ public class PackageManagerService extends IPackageManager.Stub @Nullable public PackageSetting getPackageSetting(String packageName) { - synchronized (mPackages) { + synchronized (mLock) { packageName = resolveInternalPackageNameLPr( packageName, PackageManager.VERSION_CODE_HIGHEST); return mSettings.mPackages.get(packageName); diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 0c0b93b18f84..f1ac0afa5dfd 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -41,6 +41,7 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -312,12 +313,21 @@ public abstract class PackageSettingBase extends SettingBase { } void setOverlayPaths(List<String> overlayPaths, int userId) { - modifyUserState(userId).overlayPaths = overlayPaths == null ? null : - overlayPaths.toArray(new String[overlayPaths.size()]); + modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null : + overlayPaths.toArray(new String[overlayPaths.size()])); } String[] getOverlayPaths(int userId) { - return readUserState(userId).overlayPaths; + return readUserState(userId).getOverlayPaths(); + } + + void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) { + modifyUserState(userId).setSharedLibraryOverlayPaths(libName, + overlayPaths == null ? null : overlayPaths.toArray(new String[0])); + } + + Map<String, String[]> getOverlayPathsForLibrary(int userId) { + return readUserState(userId).getSharedLibraryOverlayPaths(); } /** Only use for testing. Do NOT use in production code. */ diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index ec84b51577f9..3e665c324f0d 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4742,6 +4742,22 @@ public final class Settings { } } + Map<String, String[]> sharedLibraryOverlayPaths = + ps.getOverlayPathsForLibrary(user.id); + if (sharedLibraryOverlayPaths != null) { + for (Map.Entry<String, String[]> libOverlayPaths : + sharedLibraryOverlayPaths.entrySet()) { + if (libOverlayPaths.getValue() == null) { + continue; + } + pw.print(prefix); pw.print(" "); + pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:"); + for (String path : libOverlayPaths.getValue()) { + pw.print(prefix); pw.print(" "); pw.println(path); + } + } + } + String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id); if (lastDisabledAppCaller != null) { pw.print(prefix); pw.print(" lastDisabledCaller: "); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 9e462cd529bb..2265d010216e 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -33,11 +33,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.ParceledListSlice; +import android.content.pm.parsing.AndroidPackage; import android.content.rollback.IRollbackManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; @@ -50,6 +52,8 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.util.IntArray; @@ -61,6 +65,7 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; import java.io.File; import java.io.IOException; @@ -93,10 +98,11 @@ public class StagingManager { @GuardedBy("mStagedSessions") private final SparseIntArray mSessionRollbackIds = new SparseIntArray(); - StagingManager(PackageInstallerService pi, ApexManager am, Context context) { + StagingManager(PackageInstallerService pi, Context context) { mPi = pi; - mApexManager = am; mContext = context; + + mApexManager = ApexManager.getInstance(); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPreRebootVerificationHandler = new PreRebootVerificationHandler( BackgroundThread.get().getLooper()); @@ -334,6 +340,88 @@ public class StagingManager { return PackageHelper.getStorageManager().needsCheckpoint(); } + /** + * Apks inside apex are not installed using apk-install flow. They are scanned from the system + * directory directly by PackageManager, as such, RollbackManager need to handle their data + * separately here. + */ + private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) { + // We want to process apks inside apex. So current session needs to contain apex. + if (!sessionContainsApex(session)) { + return; + } + + boolean doSnapshotOrRestore = + (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 + || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; + if (!doSnapshotOrRestore) { + return; + } + + // Find all the apex sessions that needs processing + List<PackageInstallerSession> apexSessions = new ArrayList<>(); + if (session.isMultiPackage()) { + List<PackageInstallerSession> childrenSessions = new ArrayList<>(); + synchronized (mStagedSessions) { + for (int childSessionId : session.getChildSessionIds()) { + PackageInstallerSession childSession = mStagedSessions.get(childSessionId); + if (childSession != null) { + childrenSessions.add(childSession); + } + } + } + for (PackageInstallerSession childSession : childrenSessions) { + if (sessionContainsApex(childSession)) { + apexSessions.add(childSession); + } + } + } else { + apexSessions.add(session); + } + + // For each apex, process the apks inside it + for (PackageInstallerSession apexSession : apexSessions) { + List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName()); + for (String apk: apksInApex) { + snapshotAndRestoreApkInApexUserData(apk); + } + } + } + + private void snapshotAndRestoreApkInApexUserData(String packageName) { + IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + + PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class); + AndroidPackage pkg = mPmi.getPackage(packageName); + if (pkg == null) { + Slog.e(TAG, "Could not find package: " + packageName + + "for snapshotting/restoring user data."); + return; + } + final String seInfo = pkg.getSeInfo(); + final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); + final int[] allUsers = um.getUserIds(); + + int appId = -1; + long ceDataInode = -1; + final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName); + if (ps != null && rm != null) { + appId = ps.appId; + ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM); + // NOTE: We ignore the user specified in the InstallParam because we know this is + // an update, and hence need to restore data for all installed users. + final int[] installedUsers = ps.queryInstalledUsers(allUsers, true); + + try { + rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode, + seInfo, 0 /*token*/); + } catch (RemoteException re) { + Slog.e(TAG, "Error snapshotting/restoring user data: " + re); + } + } + } + private void resumeSession(@NonNull PackageInstallerSession session) { Slog.d(TAG, "Resuming session " + session.sessionId); @@ -407,6 +495,7 @@ public class StagingManager { abortCheckpoint(); return; } + snapshotAndRestoreApkInApexUserData(session); Slog.i(TAG, "APEX packages in session " + session.sessionId + " were successfully activated. Proceeding with APK packages, if any"); } @@ -529,7 +618,7 @@ public class StagingManager { Arrays.stream(session.getChildSessionIds()) // Retrieve cached sessions matching ids. .mapToObj(i -> mStagedSessions.get(i)) - // Filter only the ones containing APKs.s + // Filter only the ones containing APKs. .filter(childSession -> !isApexSession(childSession)) .collect(Collectors.toList()); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 6d6ec250e4cc..46893b25de9a 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -592,12 +592,6 @@ public final class DefaultPermissionGrantPolicy { getDefaultSystemHandlerActivityPackageForCategory(Intent.CATEGORY_APP_MAPS, userId), userId, ALWAYS_LOCATION_PERMISSIONS); - // Gallery - grantPermissionsToSystemPackage( - getDefaultSystemHandlerActivityPackageForCategory( - Intent.CATEGORY_APP_GALLERY, userId), - userId, STORAGE_PERMISSIONS); - // Email grantPermissionsToSystemPackage( getDefaultSystemHandlerActivityPackageForCategory( diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d921f313eb48..d468cd981b52 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -4005,21 +4005,128 @@ public class PermissionManagerService extends IPermissionManager.Stub { PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); } - if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return; - if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) { - if (requireFullPermission) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - } else { - try { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - } catch (SecurityException se) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, message); - } - } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission( + callingUid, callingUserId, userId, requireFullPermission, + requirePermissionWhenSameUser)) { + return; + } + String errorMessage = buildInvalidCrossUserPermissionMessage( + message, requireFullPermission); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + /** + * Checks if the request is from the system or an app that has the appropriate cross-user + * permissions defined as follows: + * <ul> + * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li> + * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group + * to the caller.</li> + * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group + * as the caller.</li> + * </ul> + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param message the message to log on security exception + */ + private void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, + String message) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid userId " + userId); + } + if (checkShell) { + PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission, + /*requirePermissionWhenSameUser= */ false)) { + return; + } + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); + if (isSameProfileGroup + && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) { + return; } + String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( + message, requireFullPermission, isSameProfileGroup); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + private boolean hasCrossUserPermission( + int callingUid, int callingUserId, int userId, boolean requireFullPermission, + boolean requirePermissionWhenSameUser) { + if (!requirePermissionWhenSameUser && userId == callingUserId) { + return true; + } + if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { + return true; + } + if (requireFullPermission) { + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + } + + private boolean hasPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private static String buildInvalidCrossUserPermissionMessage( + String message, boolean requireFullPermission) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message); + builder.append(": "); + } + builder.append("Requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (requireFullPermission) { + builder.append("."); + return builder.toString(); + } + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + builder.append("."); + return builder.toString(); + } + + private static String buildInvalidCrossUserOrProfilePermissionMessage( + String message, boolean requireFullPermission, boolean isSameProfileGroup) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message); + builder.append(": "); + } + builder.append("Requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (requireFullPermission) { + builder.append("."); + return builder.toString(); + } + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + if (isSameProfileGroup) { + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES); + } + builder.append("."); + return builder.toString(); } @GuardedBy({"mSettings.mLock", "mLock"}) @@ -4215,6 +4322,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, requirePermissionWhenSameUser, message); } + + @Override + public void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, String message) { + PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid, + userId, + requireFullPermission, + checkShell, + message); + } + @Override public void enforceGrantRevokeRuntimePermissionPermissions(String message) { PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message); @@ -4453,8 +4571,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void onNewUserCreated(int userId) { + mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId); synchronized (mLock) { - mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId); // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG PermissionManagerService.this.updateAllPermissions( StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 0f22619fafa6..58a9f42f372d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -271,6 +271,15 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager */ public abstract void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission, boolean checkShell, @NonNull String message); + + /** + * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)} + * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are + * in the same profile group. + */ + public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, @NonNull String message); + /** * @see #enforceCrossUserPermission(int, int, boolean, boolean, String) * @param requirePermissionWhenSameUser When {@code true}, still require the cross user diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java index 52714933c8e2..6daf5162ebad 100644 --- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -743,8 +743,8 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { // Airplane mode can be changed after ECM exits if airplane toggle button // is pressed during ECM mode - if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && - mIsWaitingForEcmExit) { + if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) + && mIsWaitingForEcmExit) { mIsWaitingForEcmExit = false; changeAirplaneModeSystemSetting(true); } diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 88c1564fdb60..9f592b85a5e6 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -62,7 +62,7 @@ class Rollback { private static final String TAG = "RollbackManager"; - @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = { + @IntDef(prefix = { "ROLLBACK_STATE_" }, value = { ROLLBACK_STATE_ENABLING, ROLLBACK_STATE_AVAILABLE, ROLLBACK_STATE_COMMITTED, @@ -92,6 +92,19 @@ class Rollback { */ static final int ROLLBACK_STATE_DELETED = 4; + @IntDef(flag = true, prefix = { "MATCH_" }, value = { + MATCH_APK_IN_APEX, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RollbackInfoFlags {} + + /** + * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex. + * These packages do not have their own sessions. They are embedded in an apex which has a + * session id. + */ + static final int MATCH_APK_IN_APEX = 1; + /** * The session ID for the staged session if this rollback data represents a staged session, * {@code -1} otherwise. @@ -323,8 +336,8 @@ class Rollback { new VersionedPackage(packageName, newVersion), new VersionedPackage(packageName, installedVersion), new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */, - isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */, - rollbackDataPolicy); + isApex, false /* isApkInApex */, new IntArray(), + new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy); synchronized (mLock) { info.getPackages().add(packageRollbackInfo); @@ -334,6 +347,30 @@ class Rollback { } /** + * Enables this rollback for the provided apk-in-apex. + * + * @return boolean True if the rollback was enabled successfully for the specified package. + */ + boolean enableForPackageInApex(String packageName, long installedVersion, + int rollbackDataPolicy) { + // TODO(b/142712057): Extract the new version number of apk-in-apex + // The new version for the apk-in-apex is set to 0 for now. If the package is then further + // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced() + // will be called and this rollback will be deleted. Other ways of package update have not + // been handled yet. + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo( + new VersionedPackage(packageName, 0 /* newVersion */), + new VersionedPackage(packageName, installedVersion), + new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */, + false /* isApex */, true /* isApkInApex */, new IntArray(), + new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy); + synchronized (mLock) { + info.getPackages().add(packageRollbackInfo); + } + return true; + } + + /** * Snapshots user data for the provided package and user ids. Does nothing if this rollback is * not in the ENABLING state. */ @@ -428,6 +465,11 @@ class Rollback { parentSessionId); for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) { + if (pkgRollbackInfo.isApkInApex()) { + // No need to issue a downgrade install request for apk-in-apex. It will + // be rolled back when its parent apex is downgraded. + continue; + } PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); String installerPackageName = mInstallerPackageName; @@ -453,7 +495,8 @@ class Rollback { this, pkgRollbackInfo.getPackageName()); if (packageCodePaths == null) { sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE, - "Backup copy of package inaccessible"); + "Backup copy of package: " + + pkgRollbackInfo.getPackageName() + " is inaccessible"); return; } @@ -696,9 +739,30 @@ class Rollback { } } - int getPackageCount() { + /** + * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback} + * instance. By default, this method does not include apk-in-apex package in the count. + * + * @param flags Apk-in-apex packages can be included in the count by passing + * {@link Rollback#MATCH_APK_IN_APEX} + * + * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback} + * according to {@code flags} passed + */ + int getPackageCount(@RollbackInfoFlags int flags) { synchronized (mLock) { - return info.getPackages().size(); + List<PackageRollbackInfo> packages = info.getPackages(); + if ((flags & MATCH_APK_IN_APEX) != 0) { + return packages.size(); + } + + int packagesWithoutApkInApex = 0; + for (PackageRollbackInfo rollbackInfo : packages) { + if (!rollbackInfo.isApkInApex()) { + packagesWithoutApkInApex++; + } + } + return packagesWithoutApkInApex; } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index e29d1a765d69..8f8a5c4b14e9 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -891,9 +891,36 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } ApplicationInfo appInfo = pkgInfo.applicationInfo; - return rollback.enableForPackage(packageName, newPackage.versionCode, + boolean success = rollback.enableForPackage(packageName, newPackage.versionCode, pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir, appInfo.splitSourceDirs, session.rollbackDataPolicy); + if (!success) { + return success; + } + + if (isApex) { + // Check if this apex contains apks inside it. If true, then they should be added as + // a RollbackPackageInfo into this rollback + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + List<String> apksInApex = pmi.getApksInApex(packageName); + for (String apkInApex : apksInApex) { + // Get information about the currently installed package. + final PackageInfo apkPkgInfo; + try { + apkPkgInfo = getPackageInfo(apkInApex); + } catch (PackageManager.NameNotFoundException e) { + // TODO: Support rolling back fresh package installs rather than + // fail here. Test this case. + Slog.e(TAG, apkInApex + " is not installed"); + return false; + } + success = rollback.enableForPackageInApex( + apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy); + if (!success) return success; + } + } + return true; } @Override @@ -907,9 +934,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { snapshotUserDataInternal(packageName, userIds); restoreUserDataInternal(packageName, userIds, appId, seInfo); - final PackageManagerInternal pmi = LocalServices.getService( - PackageManagerInternal.class); - pmi.finishPackageInstall(token, false); + // When this method is called as part of the install flow, a positive token number is + // passed to it. Need to notify the PackageManager when we are done. + if (token > 0) { + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + pmi.finishPackageInstall(token, false); + } }); } @@ -1195,7 +1226,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return null; } - if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) { + // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is + // equal to the number of sessions we are installing, to ensure we didn't skip enabling + // of any sessions. If we successfully enable an apex, then we can assume we enabled + // rollback for the embedded apk-in-apex, if any. + if (rollback.getPackageCount(0 /*flags*/) != newRollback.getPackageSessionIdCount()) { Slog.e(TAG, "Failed to enable rollback for all packages in session."); rollback.delete(mAppDataRollbackHelper); return null; diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index df75a29edd79..bbcd0def05a8 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -341,6 +341,7 @@ class RollbackStore { json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("isApex", info.isApex()); + json.put("isApkInApex", info.isApkInApex()); // Field is named 'installedUsers' for legacy reasons. json.put("installedUsers", convertToJsonArray(snapshottedUsers)); @@ -364,6 +365,7 @@ class RollbackStore { json.getJSONArray("pendingRestores")); final boolean isApex = json.getBoolean("isApex"); + final boolean isApkInApex = json.getBoolean("isApkInApex"); // Field is named 'installedUsers' for legacy reasons. final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers")); @@ -375,8 +377,8 @@ class RollbackStore { PackageManager.RollbackDataPolicy.RESTORE); return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, - pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes, - rollbackDataPolicy); + pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers, + ceSnapshotInodes, rollbackDataPolicy); } private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages) 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 0b89646bbed1..a641f06ac3ca 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -21,8 +21,10 @@ import android.annotation.Nullable; import android.hardware.audio.common.V2_0.Uuid; import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback; import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; +import android.hardware.soundtrigger.V2_3.Properties; import android.media.audio.common.AudioConfig; import android.media.audio.common.AudioOffloadInfo; +import android.media.soundtrigger_middleware.AudioCapabilities; import android.media.soundtrigger_middleware.ConfidenceLevel; import android.media.soundtrigger_middleware.ModelParameter; import android.media.soundtrigger_middleware.ModelParameterRange; @@ -69,6 +71,15 @@ class ConversionUtil { return aidlProperties; } + static @NonNull SoundTriggerModuleProperties hidl2aidlProperties( + @NonNull Properties hidlProperties) { + SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base); + aidlProperties.supportedModelArch = hidlProperties.supportedModelArch; + aidlProperties.audioCapabilities = + hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities); + return aidlProperties; + } + static @NonNull String hidl2aidlUuid(@NonNull Uuid hidlUuid) { if (hidlUuid.node == null || hidlUuid.node.length != 6) { @@ -201,16 +212,17 @@ class ConversionUtil { return hidlModel; } - static @NonNull - ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig( + static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig( @NonNull RecognitionConfig aidlConfig) { - ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig(); - hidlConfig.header.captureRequested = aidlConfig.captureRequested; + android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig = + new android.hardware.soundtrigger.V2_3.RecognitionConfig(); + hidlConfig.base.header.captureRequested = aidlConfig.captureRequested; for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) { - hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra)); + hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra)); } - hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data, + hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data, "SoundTrigger RecognitionConfig"); + hidlConfig.audioCapabilities = aidlConfig.audioCapabilities; return hidlConfig; } @@ -387,4 +399,17 @@ class ConversionUtil { return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID; } } + + static int hidl2aidlAudioCapabilities(int hidlCapabilities) { + int aidlCapabilities = 0; + if ((hidlCapabilities + & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) { + aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION; + } + if ((hidlCapabilities + & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) { + aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION; + } + return aidlCapabilities; + } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java index f0a0d8305bc6..a42d292770ae 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java @@ -66,12 +66,25 @@ class Hw2CompatUtil { return model_2_0; } - static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0( - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) { + static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1( + android.hardware.soundtrigger.V2_3.RecognitionConfig config) { + return config.base; + } + + static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0( + android.hardware.soundtrigger.V2_3.RecognitionConfig config) { android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 = - config.header; + config.base.header; // Note: this mutates the input! - config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data); + config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data); return config_2_0; } + + static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3( + android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) { + android.hardware.soundtrigger.V2_3.Properties properties_2_3 = + new android.hardware.soundtrigger.V2_3.Properties(); + properties_2_3.base = properties; + return properties_2_3; + } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java index 81252c9a8c14..8b434bd84363 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java @@ -16,7 +16,6 @@ package com.android.server.soundtrigger_middleware; -import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; import android.hardware.soundtrigger.V2_3.ModelParameterRange; import android.hidl.base.V1_0.IBase; import android.os.IHwBinder; @@ -54,9 +53,10 @@ import android.os.IHwBinder; */ public interface ISoundTriggerHw2 { /** - * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback + * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx( + * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback) */ - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties(); + android.hardware.soundtrigger.V2_3.Properties getProperties(); /** * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel, @@ -92,12 +92,12 @@ public interface ISoundTriggerHw2 { void stopAllRecognitions(); /** - * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int, - * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig, + * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int, + * android.hardware.soundtrigger.V2_3.RecognitionConfig, * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int) */ void startRecognition(int modelHandle, - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, SoundTriggerHw2Compat.Callback callback, int cookie); /** diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java index 4a852c4b68e8..2f087f46da86 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java @@ -112,18 +112,23 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { } @Override - public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() { + public android.hardware.soundtrigger.V2_3.Properties getProperties() { try { AtomicInteger retval = new AtomicInteger(-1); - AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties> + AtomicReference<android.hardware.soundtrigger.V2_3.Properties> properties = new AtomicReference<>(); - as2_0().getProperties( - (r, p) -> { - retval.set(r); - properties.set(p); - }); - handleHalStatus(retval.get(), "getProperties"); + try { + as2_3().getProperties_2_3( + (r, p) -> { + retval.set(r); + properties.set(p); + }); + } catch (NotSupported e) { + // Fall-back to the 2.0 version: + return getProperties_2_0(); + } + handleHalStatus(retval.get(), "getProperties_2_3"); return properties.get(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); @@ -212,16 +217,15 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { @Override public void startRecognition(int modelHandle, - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, Callback callback, int cookie) { try { try { - int retval = as2_1().startRecognition_2_1(modelHandle, config, - new SoundTriggerCallback(callback), cookie); - handleHalStatus(retval, "startRecognition_2_1"); + int retval = as2_3().startRecognition_2_3(modelHandle, config); + handleHalStatus(retval, "startRecognition_2_3"); } catch (NotSupported e) { // Fall-back to the 2.0 version: - startRecognition_2_0(modelHandle, config, callback, cookie); + startRecognition_2_1(modelHandle, config, callback, cookie); } } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); @@ -312,6 +316,21 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { return as2_0().interfaceDescriptor(); } + private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0() + throws RemoteException { + AtomicInteger retval = new AtomicInteger(-1); + AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties> + properties = + new AtomicReference<>(); + as2_0().getProperties( + (r, p) -> { + retval.set(r); + properties.set(p); + }); + handleHalStatus(retval.get(), "getProperties"); + return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get()); + } + private int loadSoundModel_2_0( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel, Callback callback, int cookie) @@ -349,13 +368,31 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { return handle.get(); } + private void startRecognition_2_1(int modelHandle, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, + Callback callback, int cookie) { + try { + try { + android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 = + Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config); + int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1, + new SoundTriggerCallback(callback), cookie); + handleHalStatus(retval, "startRecognition_2_1"); + } catch (NotSupported e) { + // Fall-back to the 2.0 version: + startRecognition_2_0(modelHandle, config, callback, cookie); + } + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + private void startRecognition_2_0(int modelHandle, - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, Callback callback, int cookie) throws RemoteException { - android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 = - Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config); + Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config); int retval = as2_0().startRecognition(modelHandle, config_2_0, new SoundTriggerCallback(callback), cookie); handleHalStatus(retval, "startRecognition"); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 987c05fd8c9d..5a06a2c4b388 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.PermissionChecker; import android.hardware.soundtrigger.V2_0.ISoundTriggerHw; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; @@ -32,6 +33,7 @@ import android.media.soundtrigger_middleware.RecognitionEvent; import android.media.soundtrigger_middleware.RecognitionStatus; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; +import android.media.soundtrigger_middleware.Status; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -223,23 +225,48 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic } /** - * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this - * service. + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the right permissions to use this service. */ private void checkPermissions() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO, - "Caller must have the android.permission.RECORD_AUDIO permission."); - mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, - "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission."); + enforcePermission(Manifest.permission.RECORD_AUDIO); + enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); } /** - * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt - * active sound trigger sessions. + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the right permissions to preempt active sound trigger + * sessions. */ private void checkPreemptPermissions() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER, - "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission."); + enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER); + } + + /** + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the given permission. + * + * @param permission The permission to check. + */ + private void enforcePermission(String permission) { + final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext, + permission); + switch (status) { + case PermissionChecker.PERMISSION_GRANTED: + return; + case PermissionChecker.PERMISSION_DENIED: + throw new SecurityException( + String.format("Caller must have the %s permission.", permission)); + case PermissionChecker.PERMISSION_DENIED_APP_OP: + throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED, + String.format("Caller must have the %s permission.", permission)); + default: + throw new InternalServerError( + new RuntimeException("Unexpected perimission check result.")); + } } /** State of a sound model. */ diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index 81789e1362c0..f024edecab3b 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -401,10 +401,10 @@ class SoundTriggerModule { notifyAbort(); return; } - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig = + android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig = ConversionUtil.aidl2hidlRecognitionConfig(config); - hidlConfig.header.captureDevice = mSession.mDeviceHandle; - hidlConfig.header.captureHandle = mSession.mIoHandle; + hidlConfig.base.header.captureDevice = mSession.mDeviceHandle; + hidlConfig.base.header.captureHandle = mSession.mIoHandle; mHalService.startRecognition(mHandle, hidlConfig, this, 0); setState(ModelState.ACTIVE); } diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index 8d7f8822f78e..fc7fd220b26a 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,9 +1,8 @@ -bookatz@google.com -cjyu@google.com -dwchen@google.com +jeffreyhuang@google.com joeo@google.com +muhammadq@google.com +ruchirr@google.com singhtejinder@google.com -stlafon@google.com +tsaichristine@google.com yaochen@google.com -yanglu@google.com -yro@google.com
\ No newline at end of file +yro@google.com diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java index e367f284b39a..5ee7eff20e24 100644 --- a/services/core/java/com/android/server/stats/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java @@ -16,11 +16,166 @@ package com.android.server.stats; +import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; +import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; +import static android.os.Process.getUidForPid; +import static android.os.storage.VolumeInfo.TYPE_PRIVATE; +import static android.os.storage.VolumeInfo.TYPE_PUBLIC; + +import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; +import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; +import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; +import static com.android.server.stats.ProcfsMemoryUtil.forEachPid; +import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs; +import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManagerInternal; +import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; +import android.app.AppOpsManager; +import android.app.AppOpsManager.HistoricalOps; +import android.app.AppOpsManager.HistoricalOpsRequest; +import android.app.AppOpsManager.HistoricalPackageOps; +import android.app.AppOpsManager.HistoricalUidOps; +import android.app.INotificationManager; +import android.app.ProcessMemoryState; +import android.app.StatsManager; +import android.app.StatsManager.PullAtomMetadata; +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.UidTraffic; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.content.pm.UserInfo; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; +import android.net.ConnectivityManager; +import android.net.INetworkStatsService; +import android.net.Network; +import android.net.NetworkRequest; +import android.net.NetworkStats; +import android.net.wifi.WifiManager; +import android.os.BatteryStats; +import android.os.BatteryStatsInternal; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.CoolingDevice; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.IPullAtomCallback; +import android.os.IStatsCompanionService; +import android.os.IStatsd; +import android.os.IStoraged; +import android.os.IThermalEventListener; +import android.os.IThermalService; +import android.os.Looper; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.StatFs; +import android.os.StatsLogEventWrapper; +import android.os.SynchronousResultReceiver; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Temperature; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.connectivity.WifiActivityEnergyInfo; +import android.os.storage.DiskInfo; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.provider.Settings; +import android.stats.storage.StorageEnums; +import android.telephony.ModemActivityInfo; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; import android.util.Slog; +import android.util.StatsEvent; +import android.util.StatsLog; +import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.procstats.IProcessStats; +import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.BackgroundThread; +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.internal.os.BinderCallsStats.ExportedCallStat; +import com.android.internal.os.KernelCpuSpeedReader; +import com.android.internal.os.KernelCpuThreadReader; +import com.android.internal.os.KernelCpuThreadReaderDiff; +import com.android.internal.os.KernelCpuThreadReaderSettingsObserver; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; +import com.android.internal.os.KernelWakelockReader; +import com.android.internal.os.KernelWakelockStats; +import com.android.internal.os.LooperStats; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.StoragedUidIoStatsReader; +import com.android.internal.util.DumpUtils; +import com.android.server.BinderCallsStatsService; +import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.SystemServiceManager; +import com.android.server.am.MemoryStatUtil.MemoryStat; +import com.android.server.notification.NotificationManagerService; +import com.android.server.role.RoleManagerInternal; +import com.android.server.stats.IonMemoryUtil.IonAllocations; +import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot; +import com.android.server.storage.DiskStatsFileLogger; +import com.android.server.storage.DiskStatsLoggingService; + +import com.google.android.collect.Sets; + +import libcore.io.IoUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * SystemService containing PullAtomCallbacks that are registered with statsd. @@ -31,13 +186,30 @@ public class StatsPullAtomService extends SystemService { private static final String TAG = "StatsPullAtomService"; private static final boolean DEBUG = true; + private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; + /** + * How long to wait on an individual subsystem to return its stats. + */ + private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000; + + private final Object mNetworkStatsLock = new Object(); + @GuardedBy("mNetworkStatsLock") + private INetworkStatsService mNetworkStatsService; + private final Object mThermalLock = new Object(); + @GuardedBy("mThermalLock") + private IThermalService mThermalService; + + private final Context mContext; + private StatsManager mStatsManager; + public StatsPullAtomService(Context context) { super(context); + mContext = context; } @Override public void onStart() { - // No op. + mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER); } @Override @@ -54,5 +226,846 @@ public class StatsPullAtomService extends SystemService { if (DEBUG) { Slog.d(TAG, "Registering all pullers with statsd"); } + registerWifiBytesTransfer(); + registerWifiBytesTransferBackground(); + registerMobileBytesTransfer(); + registerMobileBytesTransferBackground(); + registerBluetoothBytesTransfer(); + registerKernelWakelock(); + registerCpuTimePerFreq(); + registerCpuTimePerUid(); + registerCpuTimePerUidFreq(); + registerCpuActiveTime(); + registerCpuClusterTime(); + registerWifiActivityInfo(); + registerModemActivityInfo(); + registerBluetoothActivityInfo(); + registerSystemElapsedRealtime(); + registerSystemUptime(); + registerRemainingBatteryCapacity(); + registerFullBatteryCapacity(); + registerBatteryVoltage(); + registerBatteryLevel(); + registerBatteryCycleCount(); + registerProcessMemoryState(); + registerProcessMemoryHighWaterMark(); + registerProcessMemorySnapshot(); + registerSystemIonHeapSize(); + registerProcessSystemIonHeapSize(); + registerTemperature(); + registerCoolingDevice(); + registerBinderCalls(); + registerBinderCallsExceptions(); + registerLooperStats(); + registerDiskStats(); + registerDirectoryUsage(); + registerAppSize(); + registerCategorySize(); + registerNumFingerprintsEnrolled(); + registerNumFacesEnrolled(); + registerProcStats(); + registerProcStatsPkgProc(); + registerDiskIO(); + registerPowerProfile(); + registerProcessCpuTime(); + registerCpuTimePerThreadFreq(); + registerDeviceCalculatedPowerUse(); + registerDeviceCalculatedPowerBlameUid(); + registerDeviceCalculatedPowerBlameOther(); + registerDebugElapsedClock(); + registerDebugFailingElapsedClock(); + registerBuildInformation(); + registerRoleHolder(); + registerDangerousPermissionState(); + registerTimeZoneDataInfo(); + registerExternalStorageInfo(); + registerAppsOnExternalStorageInfo(); + registerFaceSettings(); + registerAppOps(); + registerNotificationRemoteViews(); + registerDangerousPermissionState(); + registerDangerousPermissionStateSampled(); + } + + private INetworkStatsService getINetworkStatsService() { + synchronized (mNetworkStatsLock) { + if (mNetworkStatsService == null) { + mNetworkStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + if (mNetworkStatsService != null) { + try { + mNetworkStatsService.asBinder().linkToDeath(() -> { + synchronized (mNetworkStatsLock) { + mNetworkStatsService = null; + } + }, /* flags */ 0); + } catch (RemoteException e) { + Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e); + mNetworkStatsService = null; + } + } + + } + return mNetworkStatsService; + } + } + + private IThermalService getIThermalService() { + synchronized (mThermalLock) { + if (mThermalService == null) { + mThermalService = IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + if (mThermalService != null) { + try { + mThermalService.asBinder().linkToDeath(() -> { + synchronized (mThermalLock) { + mThermalService = null; + } + }, /* flags */ 0); + } catch (RemoteException e) { + Slog.e(TAG, "linkToDeath with thermalService failed", e); + mThermalService = null; + } + } + } + return mThermalService; + } + } + private void registerWifiBytesTransfer() { + int tagId = StatsLog.WIFI_BYTES_TRANSFER; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2, 3, 4, 5}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullWifiBytesTransfer(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + // TODO: Consider caching the following call to get BatteryStatsInternal. + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getWifiIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + // Combine all the metrics per Uid into one record. + NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid(); + addNetworkStats(atomTag, pulledData, stats, false); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for wifi bytes has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void addNetworkStats( + int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) { + int size = stats.size(); + NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling + for (int j = 0; j < size; j++) { + stats.getValues(j, entry); + StatsEvent.Builder e = StatsEvent.newBuilder(); + e.setAtomId(tag); + e.writeInt(entry.uid); + if (withFGBG) { + e.writeInt(entry.set); + } + e.writeLong(entry.rxBytes); + e.writeLong(entry.rxPackets); + e.writeLong(entry.txBytes); + e.writeLong(entry.txPackets); + ret.add(e.build()); + } + } + + /** + * Allows rollups per UID but keeping the set (foreground/background) slicing. + * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java + */ + private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) { + final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1); + + final NetworkStats.Entry entry = new NetworkStats.Entry(); + entry.iface = NetworkStats.IFACE_ALL; + entry.tag = NetworkStats.TAG_NONE; + entry.metered = NetworkStats.METERED_ALL; + entry.roaming = NetworkStats.ROAMING_ALL; + + int size = stats.size(); + NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values + for (int i = 0; i < size; i++) { + stats.getValues(i, recycle); + + // Skip specific tags, since already counted in TAG_NONE + if (recycle.tag != NetworkStats.TAG_NONE) continue; + + entry.set = recycle.set; // Allows slicing by background/foreground + entry.uid = recycle.uid; + entry.rxBytes = recycle.rxBytes; + entry.rxPackets = recycle.rxPackets; + entry.txBytes = recycle.txBytes; + entry.txPackets = recycle.txPackets; + // Operations purposefully omitted since we don't use them for statsd. + ret.combineValues(entry); + } + return ret; + } + + private void registerWifiBytesTransferBackground() { + int tagId = StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {3, 4, 5, 6}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullWifiBytesTransferBackground(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getWifiIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + NetworkStats stats = rollupNetworkStatsByFGBG( + networkStatsService.getDetailedUidStats(ifaces)); + addNetworkStats(atomTag, pulledData, stats, true); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerMobileBytesTransfer() { + int tagId = StatsLog.MOBILE_BYTES_TRANSFER; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2, 3, 4, 5}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullMobileBytesTransfer(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getMobileIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + // Combine all the metrics per Uid into one record. + NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid(); + addNetworkStats(atomTag, pulledData, stats, false); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for mobile bytes has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerMobileBytesTransferBackground() { + int tagId = StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {3, 4, 5, 6}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullMobileBytesTransferBackground(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getMobileIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + NetworkStats stats = rollupNetworkStatsByFGBG( + networkStatsService.getDetailedUidStats(ifaces)); + addNetworkStats(atomTag, pulledData, stats, true); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerBluetoothBytesTransfer() { + int tagId = StatsLog.BLUETOOTH_BYTES_TRANSFER; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2, 3}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullBluetoothBytesTransfer(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + /** + * Helper method to extract the Parcelable controller info from a + * SynchronousResultReceiver. + */ + private static <T extends Parcelable> T awaitControllerInfo( + @Nullable SynchronousResultReceiver receiver) { + if (receiver == null) { + return null; + } + + try { + final SynchronousResultReceiver.Result result = + receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS); + if (result.bundle != null) { + // This is the final destination for the Bundle. + result.bundle.setDefusable(true); + + final T data = result.bundle.getParcelable(RESULT_RECEIVER_CONTROLLER_KEY); + if (data != null) { + return data; + } + } + Slog.e(TAG, "no controller energy info supplied for " + receiver.getName()); + } catch (TimeoutException e) { + Slog.w(TAG, "timeout reading " + receiver.getName() + " stats"); + } + return null; + } + + private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() { + // TODO: Investigate whether the synchronized keyword is needed. + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver( + "bluetooth"); + adapter.requestControllerActivityEnergyInfo(bluetoothReceiver); + return awaitControllerInfo(bluetoothReceiver); + } else { + Slog.e(TAG, "Failed to get bluetooth adapter!"); + return null; + } + } + + private int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + BluetoothActivityEnergyInfo info = fetchBluetoothData(); + if (info == null || info.getUidTraffic() == null) { + return StatsManager.PULL_SKIP; + } + for (UidTraffic traffic : info.getUidTraffic()) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(traffic.getUid()) + .writeLong(traffic.getRxBytes()) + .writeLong(traffic.getTxBytes()) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerKernelWakelock() { + // No op. + } + + private void pullKernelWakelock() { + // No op. + } + + private void registerCpuTimePerFreq() { + // No op. + } + + private void pullCpuTimePerFreq() { + // No op. + } + + private void registerCpuTimePerUid() { + // No op. + } + + private void pullCpuTimePerUid() { + // No op. + } + + private void registerCpuTimePerUidFreq() { + // No op. + } + + private void pullCpuTimeperUidFreq() { + // No op. + } + + private void registerCpuActiveTime() { + // No op. + } + + private void pullCpuActiveTime() { + // No op. + } + + private void registerCpuClusterTime() { + // No op. + } + + private int pullCpuClusterTime() { + return 0; + } + + private void registerWifiActivityInfo() { + // No op. + } + + private void pullWifiActivityInfo() { + // No op. + } + + private void registerModemActivityInfo() { + // No op. + } + + private void pullModemActivityInfo() { + // No op. + } + + private void registerBluetoothActivityInfo() { + int tagId = StatsLog.BLUETOOTH_ACTIVITY_INFO; + mStatsManager.registerPullAtomCallback( + tagId, + /* metadata */ null, + (atomTag, data) -> pullBluetoothActivityInfo(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) { + BluetoothActivityEnergyInfo info = fetchBluetoothData(); + if (info == null) { + return StatsManager.PULL_SKIP; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(info.getTimeStamp()) + .writeInt(info.getBluetoothStackState()) + .writeLong(info.getControllerTxTimeMillis()) + .writeLong(info.getControllerRxTimeMillis()) + .writeLong(info.getControllerIdleTimeMillis()) + .writeLong(info.getControllerEnergyUsed()) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerSystemElapsedRealtime() { + // No op. + } + + private void pullSystemElapsedRealtime() { + // No op. + } + + private void registerSystemUptime() { + // No op. + } + + private void pullSystemUptime() { + // No op. + } + + private void registerRemainingBatteryCapacity() { + // No op. + } + + private void pullRemainingBatteryCapacity() { + // No op. + } + + private void registerFullBatteryCapacity() { + // No op. + } + + private void pullFullBatteryCapacity() { + // No op. + } + + private void registerBatteryVoltage() { + // No op. + } + + private void pullBatteryVoltage() { + // No op. + } + + private void registerBatteryLevel() { + // No op. + } + + private void pullBatteryLevel() { + // No op. + } + + private void registerBatteryCycleCount() { + // No op. + } + + private void pullBatteryCycleCount() { + // No op. + } + + private void registerProcessMemoryState() { + // No op. + } + + private void pullProcessMemoryState() { + // No op. + } + + private void registerProcessMemoryHighWaterMark() { + // No op. + } + + private void pullProcessMemoryHighWaterMark() { + // No op. + } + + private void registerProcessMemorySnapshot() { + // No op. + } + + private void pullProcessMemorySnapshot() { + // No op. + } + + private void registerSystemIonHeapSize() { + // No op. + } + + private void pullSystemIonHeapSize() { + // No op. + } + + private void registerProcessSystemIonHeapSize() { + // No op. + } + + private void pullProcessSystemIonHeapSize() { + // No op. + } + + private void registerTemperature() { + // No op. + } + + private void pullTemperature() { + // No op. + } + + private void registerCoolingDevice() { + // No op. + } + + private void pullCooldownDevice() { + // No op. + } + + private void registerBinderCalls() { + // No op. + } + + private void pullBinderCalls() { + // No op. + } + + private void registerBinderCallsExceptions() { + // No op. + } + + private void pullBinderCallsExceptions() { + // No op. + } + + private void registerLooperStats() { + // No op. + } + + private void pullLooperStats() { + // No op. + } + + private void registerDiskStats() { + // No op. + } + + private void pullDiskStats() { + // No op. + } + + private void registerDirectoryUsage() { + // No op. + } + + private void pullDirectoryUsage() { + // No op. + } + + private void registerAppSize() { + // No op. + } + + private void pullAppSize() { + // No op. + } + + private void registerCategorySize() { + // No op. + } + + private void pullCategorySize() { + // No op. + } + + private void registerNumFingerprintsEnrolled() { + // No op. + } + + private void pullNumFingerprintsEnrolled() { + // No op. + } + + private void registerNumFacesEnrolled() { + // No op. + } + + private void pullNumFacesEnrolled() { + // No op. + } + + private void registerProcStats() { + // No op. + } + + private void pullProcStats() { + // No op. + } + + private void registerProcStatsPkgProc() { + // No op. + } + + private void pullProcStatsPkgProc() { + // No op. + } + + private void registerDiskIO() { + // No op. + } + + private void pullDiskIO() { + // No op. + } + + private void registerPowerProfile() { + int tagId = StatsLog.POWER_PROFILE; + mStatsManager.registerPullAtomCallback( + tagId, + /* PullAtomMetadata */ null, + (atomTag, data) -> pullPowerProfile(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullPowerProfile(int atomTag, List<StatsEvent> pulledData) { + PowerProfile powerProfile = new PowerProfile(mContext); + ProtoOutputStream proto = new ProtoOutputStream(); + powerProfile.dumpDebug(proto); + proto.flush(); + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeByteArray(proto.getBytes()) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerProcessCpuTime() { + // No op. + } + + private void pullProcessCpuTime() { + // No op. + } + + private void registerCpuTimePerThreadFreq() { + // No op. + } + + private void pullCpuTimePerThreadFreq() { + // No op. + } + + private void registerDeviceCalculatedPowerUse() { + // No op. + } + + private void pullDeviceCalculatedPowerUse() { + // No op. + } + + private void registerDeviceCalculatedPowerBlameUid() { + // No op. + } + + private void pullDeviceCalculatedPowerBlameUid() { + // No op. + } + + private void registerDeviceCalculatedPowerBlameOther() { + // No op. + } + + private void pullDeviceCalculatedPowerBlameOther() { + // No op. + } + + private void registerDebugElapsedClock() { + // No op. + } + + private void pullDebugElapsedClock() { + // No op. + } + + private void registerDebugFailingElapsedClock() { + // No op. + } + + private void pullDebugFailingElapsedClock() { + // No op. + } + + private void registerBuildInformation() { + // No op. + } + + private void pullBuildInformation() { + // No op. + } + + private void registerRoleHolder() { + // No op. + } + + private void pullRoleHolder() { + // No op. + } + + private void registerDangerousPermissionState() { + // No op. + } + + private void pullDangerousPermissionState() { + // No op. + } + + private void registerTimeZoneDataInfo() { + // No op. + } + + private void pullTimeZoneDataInfo() { + // No op. + } + + private void registerExternalStorageInfo() { + // No op. + } + + private void pullExternalStorageInfo() { + // No op. + } + + private void registerAppsOnExternalStorageInfo() { + // No op. + } + + private void pullAppsOnExternalStorageInfo() { + // No op. + } + + private void registerFaceSettings() { + // No op. + } + + private void pullRegisterFaceSettings() { + // No op. + } + + private void registerAppOps() { + // No op. + } + + private void pullAppOps() { + // No op. + } + + private void registerNotificationRemoteViews() { + // No op. + } + + private void pullNotificationRemoteViews() { + // No op. + } + + private void registerDangerousPermissionStateSampled() { + // No op. + } + + private void pullDangerousPermissionStateSampled() { + // No op. } } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index f661b5e0d8a8..c96479543b3a 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; import android.os.TimestampedValue; import java.io.PrintWriter; @@ -73,9 +72,6 @@ public interface TimeDetectorStrategy { /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */ void releaseWakeLock(); - - /** Send the supplied intent as a stick broadcast. */ - void sendStickyBroadcast(@NonNull Intent intent); } /** Initialize the strategy. */ diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java index 42d59d51c6af..9b89d9437fc3 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java @@ -20,11 +20,9 @@ import android.annotation.NonNull; import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; @@ -112,11 +110,6 @@ public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrat mWakeLock.release(); } - @Override - public void sendStickyBroadcast(@NonNull Intent intent) { - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - private void checkWakeLockHeld() { if (!mWakeLock.isHeld()) { Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held"); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index da848d87ba71..e95fc4a4a938 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -23,9 +23,7 @@ import android.app.AlarmManager; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; import android.os.TimestampedValue; -import android.telephony.TelephonyManager; import android.util.LocalLog; import android.util.Slog; @@ -535,17 +533,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } else { mLastAutoSystemClockTimeSet = null; } - - // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only - // when setting the time using NITZ. - if (origin == ORIGIN_PHONE) { - // Send a broadcast that telephony code used to send after setting the clock. - // TODO Remove this broadcast as soon as there are no remaining listeners. - Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra("time", newSystemClockMillis); - mCallback.sendStickyBroadcast(intent); - } } /** diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5b58199aec4f..eb7c5caa62aa 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -127,6 +127,9 @@ public final class TvInputManagerService extends SystemService { // A map from user id to UserState. private final SparseArray<UserState> mUserStates = new SparseArray<>(); + // A map from session id to session state saved in userstate + private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>(); + private final WatchLogHandler mWatchLogHandler; public TvInputManagerService(Context context) { @@ -632,7 +635,8 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(userId); SessionState sessionState = userState.sessionStateMap.get(sessionToken); if (DEBUG) { - Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")"); + Slog.d(TAG, "createSessionInternalLocked(inputId=" + + sessionState.inputId + ", sessionId=" + sessionState.sessionId + ")"); } InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); @@ -643,9 +647,11 @@ public final class TvInputManagerService extends SystemService { // Create a session. When failed, send a null token immediately. try { if (sessionState.isRecordingSession) { - service.createRecordingSession(callback, sessionState.inputId); + service.createRecordingSession( + callback, sessionState.inputId, sessionState.sessionId); } else { - service.createSession(channels[1], callback, sessionState.inputId); + service.createSession( + channels[1], callback, sessionState.inputId, sessionState.sessionId); } } catch (RemoteException e) { Slog.e(TAG, "error in createSession", e); @@ -715,6 +721,8 @@ public final class TvInputManagerService extends SystemService { } } + mSessionIdToSessionStateMap.remove(sessionState.sessionId); + ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName); if (serviceState != null) { serviceState.sessionTokens.remove(sessionToken); @@ -1156,9 +1164,11 @@ public final class TvInputManagerService extends SystemService { public void createSession(final ITvInputClient client, final String inputId, boolean isRecordingSession, int seq, int userId) { final int callingUid = Binder.getCallingUid(); - final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, "createSession"); final long identity = Binder.clearCallingIdentity(); + StringBuilder sessionId = new StringBuilder(); try { synchronized (mLock) { if (userId != mCurrentUserId && !isRecordingSession) { @@ -1187,15 +1197,21 @@ public final class TvInputManagerService extends SystemService { return; } + // Create a unique session id with pid, uid and resolved user id + sessionId.append(callingUid).append(callingPid).append(resolvedUserId); + // Create a new session token and a session state. IBinder sessionToken = new Binder(); SessionState sessionState = new SessionState(sessionToken, info.getId(), info.getComponent(), isRecordingSession, client, seq, callingUid, - resolvedUserId); + callingPid, resolvedUserId, sessionId.toString()); // Add them to the global session state map of the current user. userState.sessionStateMap.put(sessionToken, sessionState); + // Map the session id to the sessionStateMap in the user state + mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState); + // Also, add them to the session state map of the current service. serviceState.sessionTokens.add(sessionToken); @@ -2003,6 +2019,43 @@ public final class TvInputManagerService extends SystemService { } @Override + public int getClientPid(String sessionId) { + ensureTunerResourceAccessPermission(); + final long identity = Binder.clearCallingIdentity(); + + int clientPid = TvInputManager.UNKNOWN_CLIENT_PID; + try { + synchronized (mLock) { + try { + clientPid = getClientPidLocked(sessionId); + } catch (ClientPidNotFoundException e) { + Slog.e(TAG, "error in getClientPid", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + return clientPid; + } + + private int getClientPidLocked(String sessionId) + throws IllegalStateException { + if (mSessionIdToSessionStateMap.get(sessionId) == null) { + throw new IllegalStateException("Client Pid not found with sessionId " + + sessionId); + } + return mSessionIdToSessionStateMap.get(sessionId).callingPid; + } + + private void ensureTunerResourceAccessPermission() { + if (mContext.checkCallingPermission( + android.Manifest.permission.TUNER_RESOURCE_ACCESS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires TUNER_RESOURCE_ACCESS permission"); + } + } + + @Override @SuppressWarnings("resource") protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -2094,9 +2147,11 @@ public final class TvInputManagerService extends SystemService { pw.increaseIndent(); pw.println("inputId: " + session.inputId); + pw.println("sessionId: " + session.sessionId); pw.println("client: " + session.client); pw.println("seq: " + session.seq); pw.println("callingUid: " + session.callingUid); + pw.println("callingPid: " + session.callingPid); pw.println("userId: " + session.userId); pw.println("sessionToken: " + session.sessionToken); pw.println("session: " + session.session); @@ -2226,11 +2281,13 @@ public final class TvInputManagerService extends SystemService { private final class SessionState implements IBinder.DeathRecipient { private final String inputId; + private final String sessionId; private final ComponentName componentName; private final boolean isRecordingSession; private final ITvInputClient client; private final int seq; private final int callingUid; + private final int callingPid; private final int userId; private final IBinder sessionToken; private ITvInputSession session; @@ -2240,7 +2297,7 @@ public final class TvInputManagerService extends SystemService { private SessionState(IBinder sessionToken, String inputId, ComponentName componentName, boolean isRecordingSession, ITvInputClient client, int seq, int callingUid, - int userId) { + int callingPid, int userId, String sessionId) { this.sessionToken = sessionToken; this.inputId = inputId; this.componentName = componentName; @@ -2248,7 +2305,9 @@ public final class TvInputManagerService extends SystemService { this.client = client; this.seq = seq; this.callingUid = callingUid; + this.callingPid = callingPid; this.userId = userId; + this.sessionId = sessionId; } @Override @@ -2962,4 +3021,10 @@ public final class TvInputManagerService extends SystemService { super(name); } } + + private static class ClientPidNotFoundException extends IllegalArgumentException { + public ClientPidNotFoundException(String name) { + super(name); + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 26d76a8d6e28..320be2d7cdbd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5934,7 +5934,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAnimationBoundsLayer = createAnimationBoundsLayer(t); // Crop to stack bounds. - t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); + if (!WindowManagerService.sHierarchicalAnimations) { + // For Hierarchical animation, we don't need to set window crop since the leash + // surface size has already same as the animating container. + t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); + } t.setLayer(mAnimationBoundsLayer, layer); // Reparent leash to animation bounds layer. @@ -5982,9 +5986,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } clearThumbnail(); + final Transaction transaction = getAnimatingContainer().getPendingTransaction(); mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, - getPendingTransaction(), this, thumbnailHeader); - mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader)); + transaction, getAnimatingContainer(), thumbnailHeader); + mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader)); } /** @@ -6011,13 +6016,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (thumbnail == null) { return; } + final Transaction transaction = getAnimatingContainer().getPendingTransaction(); mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, - getPendingTransaction(), this, thumbnail); + transaction, getAnimatingContainer(), thumbnail); final Animation animation = getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked( win.getFrameLw()); - mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left, - frame.top)); + mThumbnail.startAnimation(transaction, animation, new Point(frame.left, frame.top)); } private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index c95943904d1f..9fd3ea4fc090 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -163,6 +163,7 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.ITaskOrganizer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -803,6 +804,16 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, false /* creating */); + + windowingMode = getWindowingMode(); + /* + * Different windowing modes may be managed by different task organizers. If + * getTaskOrganizer returns null, we still call transferToTaskOrganizer to + * make sure we clear it. + */ + final ITaskOrganizer org = + mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + transferToTaskOrganizer(org); } /** @@ -1650,6 +1661,33 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } /** + * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't + * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking + * the first one is ok. + */ + boolean isControlledByTaskOrganizer() { + return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null; + } + + private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) { + tr.setTaskOrganizer(organizer); + } + + /** + * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer. + * This will (or shortly there-after) invoke the taskAppeared callbacks. + * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of + * emitting the taskVanished callbacks. + */ + void transferToTaskOrganizer(ITaskOrganizer organizer) { + final PooledConsumer c = PooledLambda.obtainConsumer( + ActivityStack::transferSingleTaskToOrganizer, + PooledLambda.__(Task.class), organizer); + forAllTasks(c); + c.recycle(); + } + + /** * Returns true if the stack should be visible. * * @param starting The currently starting activity or null if there is none. @@ -3577,6 +3615,15 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration, boolean fromFullscreen) { if (!inPinnedWindowingMode()) return; + + /** + * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation. + * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely + * on the TaskOrganizer side, so we just hand over the leash without doing any animation. + * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer + * needs to have flexibility to schedule that at an appropriate point in the animation. + */ + if (isControlledByTaskOrganizer()) return; if (toBounds == null /* toFullscreen */) { final Configuration parentConfig = getParent().getConfiguration(); final ActivityRecord top = topRunningNonOverlayTaskActivity(); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index d63165adb2f8..aa90248b97f8 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -2548,6 +2548,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityRecord::updatePictureInPictureMode, PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate); + task.getStack().setBounds(targetStackBounds); task.forAllActivities(c); c.recycle(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 474c5c960eb0..ded603c9fd77 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -226,6 +226,7 @@ import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowContainerTransaction; @@ -662,6 +663,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private FontScaleSettingObserver mFontScaleSettingObserver; + /** + * Stores the registration and state of TaskOrganizers in use. + */ + TaskOrganizerController mTaskOrganizerController = + new TaskOrganizerController(this, mGlobalLock); + private int mDeviceOwnerUid = Process.INVALID_UID; private final class FontScaleSettingObserver extends ContentObserver { @@ -1271,6 +1278,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .execute(); } + @Override + public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + enforceCallerIsRecentsOrHasPermission( + MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()"); + synchronized (mGlobalLock) { + mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode); + } + } @Override public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) { @@ -3319,6 +3334,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private void applyWindowContainerChange(ConfigurationContainer cc, + WindowContainerTransaction.Change c) { + sanitizeAndApplyConfigChange(cc, c); + + Rect enterPipBounds = c.getEnterPipBounds(); + if (enterPipBounds != null) { + Task tr = (Task) cc; + mStackSupervisor.updatePictureInPictureMode(tr, + enterPipBounds, true); + } + } + @Override public void applyContainerTransaction(WindowContainerTransaction t) { mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()"); @@ -3335,7 +3362,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { entries.next(); final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder( entry.getKey()).getContainer(); - sanitizeAndApplyConfigChange(cc, entry.getValue()); + applyWindowContainerChange(cc, entry.getValue()); } } } finally { @@ -4057,7 +4084,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { throw new IllegalArgumentException("Stack: " + stack + " doesn't support animated resize."); } - if (animate) { + /** + * TODO(b/146594635): Remove all PIP animation code from WM + * once SysUI handles animation. Don't even try to animate TaskOrganized tasks. + */ + if (animate && !stack.isControlledByTaskOrganizer()) { stack.animateResizePinnedStack(null /* destBounds */, null /* sourceHintBounds */, animationDuration, false /* fromFullscreen */); diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index dd3365c900d7..d0310f1a7607 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -666,4 +666,8 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return sb.toString(); } } + + RemoteToken getRemoteToken() { + return mRemoteToken; + } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f9ad03f40723..9c62e9970b48 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2061,7 +2061,8 @@ public class DisplayPolicy { cf.set(displayFrames.mRestricted); } applyStableConstraints(sysUiFl, fl, cf, displayFrames); - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); @@ -2138,7 +2139,8 @@ public class DisplayPolicy { applyStableConstraints(sysUiFl, fl, cf, displayFrames); - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); @@ -2179,7 +2181,8 @@ public class DisplayPolicy { cf.set(displayFrames.mContent); df.set(displayFrames.mContent); } - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index c4b67d76607e..091f66c0b19a 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -279,6 +279,19 @@ final class InputMonitor { // we avoid reintroducing this concept by just choosing one of them here. inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left; + /** + * If the window is in a TaskManaged by a TaskOrganizer then most cropping + * will be applied using the SurfaceControl hierarchy from the Organizer. + * This means we need to make sure that these changes in crop are reflected + * in the input windows, and so ensure this flag is set so that + * the input crop always reflects the surface hierarchy. + * we may have some issues with modal-windows, but I guess we can + * cross that bridge when we come to implementing full-screen TaskOrg + */ + if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) { + inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */); + } + if (child.mGlobalScale != 1) { // If we are scaling the window, input coordinates need // to be inversely scaled to map from what is on screen diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index a13383d3991e..5a591ecd4746 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -129,6 +129,7 @@ class InsetsSourceProvider { if (win == null) { setServerVisible(false); mSource.setFrame(new Rect()); + mSource.setVisibleFrame(null); } else if (mControllable) { mWin.setControllableInsetProvider(this); if (mControlTarget != null) { @@ -160,6 +161,15 @@ class InsetsSourceProvider { mTmpRect.inset(mWin.mGivenContentInsets); } mSource.setFrame(mTmpRect); + + if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0 + || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) { + mTmpRect.set(mWin.getFrameLw()); + mTmpRect.inset(mWin.mGivenVisibleInsets); + mSource.setVisibleFrame(mTmpRect); + } else { + mSource.setVisibleFrame(null); + } } /** diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index a7bf6600d7b5..c3e815d10dda 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2168,12 +2168,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService.continueWindowLayout(); } + // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation. // Notify the pinned stack controller to prepare the PiP animation, expect callback - // delivered from SystemUI to WM to start the animation. - final PinnedStackController pinnedStackController = + // delivered from SystemUI to WM to start the animation. Unless we are using + // the TaskOrganizer in which case the animation will be entirely handled + // on that side. + if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED) + == null) { + final PinnedStackController pinnedStackController = display.mDisplayContent.getPinnedStackController(); - pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio, - null /* stackBounds */); + pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio, + null /* stackBounds */); + } // TODO: revisit the following statement after the animation is moved from WM to SysUI. // Update the visibility of all activities after the they have been reparented to the new diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index eaa0ea72452a..399c5d3ae45f 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -16,12 +16,8 @@ package com.android.server.wm; -import static com.android.server.wm.AnimationSpecProto.ROTATE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; -import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS; -import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA; -import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA; import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -29,9 +25,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; -import android.animation.ArgbEvaluator; import android.content.Context; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -46,9 +40,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -import com.android.internal.R; import com.android.server.protolog.common.ProtoLog; -import com.android.server.wm.utils.RotationAnimationUtils; import java.io.PrintWriter; @@ -68,10 +60,10 @@ import java.io.PrintWriter; * animation first rotate the new content into the old orientation to then be able to * animate to the new orientation * - * <li> The Background color frame: <p> - * To have the animation seem more seamless, we add a color transitioning background behind the - * exiting and entering layouts. We compute the brightness of the start and end - * layouts and transition from the two brightness values as grayscale underneath the animation + * <li> The exiting Blackframe: <p> + * Because the change of orientation might change the width and height of the content (i.e + * when rotating from portrait to landscape) we "crop" the new content using black frames + * around the screenshot so the new content does not go beyond the screenshot's bounds * * <li> The entering Blackframe: <p> * The enter Blackframe is similar to the exit Blackframe but is only used when a custom @@ -89,6 +81,8 @@ class ScreenRotationAnimation { */ private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER; private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE; + private static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1; + private static final int SCREEN_FREEZE_LAYER_EXIT = SCREEN_FREEZE_LAYER_BASE + 2; private final Context mContext; private final DisplayContent mDisplayContent; @@ -96,18 +90,16 @@ class ScreenRotationAnimation { private final Transformation mRotateExitTransformation = new Transformation(); private final Transformation mRotateEnterTransformation = new Transformation(); // Complete transformations being applied. + private final Transformation mExitTransformation = new Transformation(); private final Transformation mEnterTransformation = new Transformation(); + private final Matrix mFrameInitialMatrix = new Matrix(); private final Matrix mSnapshotInitialMatrix = new Matrix(); + private final Matrix mSnapshotFinalMatrix = new Matrix(); + private final Matrix mExitFrameFinalMatrix = new Matrix(); private final WindowManagerService mService; - /** Only used for custom animations and not screen rotation. */ private SurfaceControl mEnterBlackFrameLayer; - /** This layer contains the actual screenshot that is to be faded out. */ - private SurfaceControl mScreenshotLayer; - /** - * Only used for screen rotation and not custom animations. Layered behind all other layers - * to avoid showing any "empty" spots - */ - private SurfaceControl mBackColorSurface; + private SurfaceControl mRotationLayer; + private SurfaceControl mSurfaceControl; private BlackFrame mEnteringBlackFrame; private int mWidth, mHeight; @@ -128,11 +120,8 @@ class ScreenRotationAnimation { private boolean mFinishAnimReady; private long mFinishAnimStartTime; private boolean mForceDefaultOrientation; + private BlackFrame mExitingBlackFrame; private SurfaceRotationAnimationController mSurfaceRotationAnimationController; - /** Intensity of light/whiteness of the layout before rotation occurs. */ - private float mStartLuma; - /** Intensity of light/whiteness of the layout after rotation occurs. */ - private float mEndLuma; public ScreenRotationAnimation(Context context, DisplayContent displayContent, boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) { @@ -173,15 +162,9 @@ class ScreenRotationAnimation { final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { - mBackColorSurface = displayContent.makeChildSurface(null) - .setName("BackColorSurface") - .setColorLayer() - .build(); - - mScreenshotLayer = displayContent.makeOverlay() + mRotationLayer = displayContent.makeOverlay() .setName("RotationLayer") - .setBufferSize(mWidth, mHeight) - .setSecure(isSecure) + .setContainerLayer() .build(); mEnterBlackFrameLayer = displayContent.makeOverlay() @@ -189,21 +172,26 @@ class ScreenRotationAnimation { .setContainerLayer() .build(); + mSurfaceControl = mService.makeSurfaceBuilder(null) + .setName("ScreenshotSurface") + .setParent(mRotationLayer) + .setBufferSize(mWidth, mHeight) + .setSecure(isSecure) + .build(); + // In case display bounds change, screenshot buffer and surface may mismatch so set a // scaling mode. SurfaceControl.Transaction t2 = mService.mTransactionFactory.get(); - t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW); + t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW); t2.apply(true /* sync */); // Capture a screenshot into the surface we just created. final int displayId = display.getDisplayId(); final Surface surface = mService.mSurfaceFactory.get(); - surface.copyFrom(mScreenshotLayer); + surface.copyFrom(mSurfaceControl); SurfaceControl.ScreenshotGraphicBuffer gb = mService.mDisplayManagerInternal.screenshot(displayId); if (gb != null) { - mStartLuma = RotationAnimationUtils.getAvgBorderLuma(gb.getGraphicBuffer(), - gb.getColorSpace()); try { surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(), gb.getColorSpace()); @@ -214,15 +202,13 @@ class ScreenRotationAnimation { // screenshot surface we display it in also has FLAG_SECURE so that // the user can not screenshot secure layers via the screenshot surface. if (gb.containsSecureLayers()) { - t.setSecure(mScreenshotLayer, true); + t.setSecure(mSurfaceControl, true); } - t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE); - t.reparent(mBackColorSurface, displayContent.getSurfaceControl()); - t.setLayer(mBackColorSurface, -1); - t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); - t.setAlpha(mBackColorSurface, 1); - t.show(mScreenshotLayer); - t.show(mBackColorSurface); + t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE); + t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT); + t.setAlpha(mSurfaceControl, 0); + t.show(mRotationLayer); + t.show(mSurfaceControl); } else { Slog.w(TAG, "Unable to take screenshot of display " + displayId); } @@ -232,11 +218,32 @@ class ScreenRotationAnimation { } ProtoLog.i(WM_SHOW_SURFACE_ALLOC, - " FREEZE %s: CREATE", mScreenshotLayer); + " FREEZE %s: CREATE", mSurfaceControl); setRotation(t, originalRotation); t.apply(); } + private static void createRotationMatrix(int rotation, int width, int height, + Matrix outMatrix) { + switch (rotation) { + case Surface.ROTATION_0: + outMatrix.reset(); + break; + case Surface.ROTATION_90: + outMatrix.setRotate(90, 0, 0); + outMatrix.postTranslate(height, 0); + break; + case Surface.ROTATION_180: + outMatrix.setRotate(180, 0, 0); + outMatrix.postTranslate(width, height); + break; + case Surface.ROTATION_270: + outMatrix.setRotate(270, 0, 0); + outMatrix.postTranslate(0, width); + break; + } + } + public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(STARTED, mStarted); @@ -245,11 +252,11 @@ class ScreenRotationAnimation { } boolean hasScreenshot() { - return mScreenshotLayer != null; + return mSurfaceControl != null; } private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) { - if (mScreenshotLayer == null) { + if (mRotationLayer == null) { return; } matrix.getValues(mTmpFloats); @@ -260,19 +267,24 @@ class ScreenRotationAnimation { x -= mCurrentDisplayRect.left; y -= mCurrentDisplayRect.top; } - t.setPosition(mScreenshotLayer, x, y); - t.setMatrix(mScreenshotLayer, + t.setPosition(mRotationLayer, x, y); + t.setMatrix(mRotationLayer, mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); - t.setAlpha(mScreenshotLayer, (float) 1.0); - t.show(mScreenshotLayer); + t.setAlpha(mSurfaceControl, (float) 1.0); + t.setAlpha(mRotationLayer, (float) 1.0); + t.show(mRotationLayer); } public void printTo(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer); + pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl); pw.print(" mWidth="); pw.print(mWidth); pw.print(" mHeight="); pw.println(mHeight); + pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame); + if (mExitingBlackFrame != null) { + mExitingBlackFrame.printTo(prefix + " ", pw); + } pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame); @@ -291,10 +303,20 @@ class ScreenRotationAnimation { pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation); pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mExitTransformation="); + mExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mEnterTransformation="); mEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mFrameInitialMatrix="); + mFrameInitialMatrix.printShortString(pw); + pw.println(); pw.print(prefix); pw.print("mSnapshotInitialMatrix="); - mSnapshotInitialMatrix.printShortString(pw);pw.println(); + mSnapshotInitialMatrix.printShortString(pw); + pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw); + pw.println(); + pw.print(prefix); pw.print("mExitFrameFinalMatrix="); + mExitFrameFinalMatrix.printShortString(pw); + pw.println(); pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation); if (mForceDefaultOrientation) { pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString()); @@ -309,7 +331,7 @@ class ScreenRotationAnimation { // to the snapshot to make it stay in the same original position // with the current screen rotation. int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0); - RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); + createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); setRotationTransform(t, mSnapshotInitialMatrix); } @@ -319,7 +341,7 @@ class ScreenRotationAnimation { */ private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mScreenshotLayer == null) { + if (mSurfaceControl == null) { // Can't do animation. return false; } @@ -332,58 +354,89 @@ class ScreenRotationAnimation { // Figure out how the screen has moved from the original rotation. int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation); + mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_alpha); final boolean customAnim; if (exitAnim != 0 && enterAnim != 0) { customAnim = true; mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim); - mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_alpha); } else { customAnim = false; - switch (delta) { /* Counter-Clockwise Rotations */ + switch (delta) { case Surface.ROTATION_0: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_0_exit); + com.android.internal.R.anim.screen_rotate_0_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_0_enter); + com.android.internal.R.anim.screen_rotate_0_enter); break; case Surface.ROTATION_90: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_plus_90_exit); + com.android.internal.R.anim.screen_rotate_plus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_plus_90_enter); + com.android.internal.R.anim.screen_rotate_plus_90_enter); break; case Surface.ROTATION_180: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_180_exit); + com.android.internal.R.anim.screen_rotate_180_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_180_enter); + com.android.internal.R.anim.screen_rotate_180_enter); break; case Surface.ROTATION_270: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_minus_90_exit); + com.android.internal.R.anim.screen_rotate_minus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_minus_90_enter); + com.android.internal.R.anim.screen_rotate_minus_90_enter); break; } } + // Initialize the animations. This is a hack, redefining what "parent" + // means to allow supplying the last and next size. In this definition + // "%p" is the original (let's call it "previous") size, and "%" is the + // screen's current/new size. + mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + mAnimRunning = false; + mFinishAnimReady = false; + mFinishAnimStartTime = -1; + mRotateExitAnimation.restrictDuration(maxAnimationDuration); mRotateExitAnimation.scaleCurrentDuration(animationScale); - mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateEnterAnimation.restrictDuration(maxAnimationDuration); mRotateEnterAnimation.scaleCurrentDuration(animationScale); + mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); + mRotateAlphaAnimation.scaleCurrentDuration(animationScale); - mAnimRunning = false; - mFinishAnimReady = false; - mFinishAnimStartTime = -1; - - if (customAnim) { - mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); - mRotateAlphaAnimation.scaleCurrentDuration(animationScale); + if (!customAnim && mExitingBlackFrame == null) { + try { + // Compute the transformation matrix that must be applied + // the the black frame to make it stay in the initial position + // before the new screen rotation. This is different than the + // snapshot transformation because the snapshot is always based + // of the native orientation of the screen, not the orientation + // we were last in. + createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix); + + final Rect outer; + final Rect inner; + if (mForceDefaultOrientation) { + // Going from a smaller Display to a larger Display, add curtains to sides + // or top and bottom. Going from a larger to smaller display will result in + // no BlackSurfaces being constructed. + outer = mCurrentDisplayRect; + inner = mOriginalDisplayRect; + } else { + outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2); + inner = new Rect(0, 0, mWidth, mHeight); + } + mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner, + SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation, + mRotationLayer); + } catch (OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } } if (customAnim && mEnteringBlackFrame == null) { @@ -398,12 +451,7 @@ class ScreenRotationAnimation { } } - if (customAnim) { - mSurfaceRotationAnimationController.startCustomAnimation(); - } else { - mSurfaceRotationAnimationController.startScreenRotationAnimation(); - } - + mSurfaceRotationAnimationController.startAnimation(); return true; } @@ -412,13 +460,11 @@ class ScreenRotationAnimation { */ public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mScreenshotLayer == null) { + if (mSurfaceControl == null) { // Can't do animation. return false; } if (!mStarted) { - mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(), - mDisplayContent.getWindowingLayer()); startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight, exitAnim, enterAnim); } @@ -434,28 +480,28 @@ class ScreenRotationAnimation { mSurfaceRotationAnimationController.cancel(); mSurfaceRotationAnimationController = null; } - - if (mScreenshotLayer != null) { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer); + if (mSurfaceControl != null) { + ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mSurfaceControl); + mSurfaceControl = null; SurfaceControl.Transaction t = mService.mTransactionFactory.get(); - if (mScreenshotLayer.isValid()) { - t.remove(mScreenshotLayer); + if (mRotationLayer != null) { + if (mRotationLayer.isValid()) { + t.remove(mRotationLayer); + } + mRotationLayer = null; } - mScreenshotLayer = null; - if (mEnterBlackFrameLayer != null) { if (mEnterBlackFrameLayer.isValid()) { t.remove(mEnterBlackFrameLayer); } mEnterBlackFrameLayer = null; } - if (mBackColorSurface != null) { - t.remove(mBackColorSurface); - mBackColorSurface = null; - } t.apply(); } - + if (mExitingBlackFrame != null) { + mExitingBlackFrame.kill(); + mExitingBlackFrame = null; + } if (mEnteringBlackFrame != null) { mEnteringBlackFrame.kill(); mEnteringBlackFrame = null; @@ -491,28 +537,18 @@ class ScreenRotationAnimation { * Utility class that runs a {@link ScreenRotationAnimation} on the {@link * SurfaceAnimationRunner}. * <p> - * The rotation animation supports both screen rotation and custom animations - * - * For custom animations: - * <ul> - * <li> - * The screenshot layer which has an added animation of it's alpha channel - * ("screen_rotate_alpha") and that will be applied along with the custom animation. - * </li> - * <li> A device layer that is animated with the provided custom animation </li> - * </ul> - * - * For screen rotation: + * The rotation animation is divided into the following hierarchy: * <ul> - * <li> A rotation layer that is both rotated and faded out during a single animation </li> - * <li> A device layer that is both rotated and faded in during a single animation </li> - * <li> A background color layer that transitions colors behind the first two layers </li> - * </ul> - * + * <li> A first rotation layer, containing the blackframes. This layer is animated by the + * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation. + * <ul> + * <li> A child layer containing the screenshot on which is added an animation of it's + * alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li> + * </ul> + * <li> A second rotation layer used when custom animations are passed in * {@link ScreenRotationAnimation#startAnimation( * SurfaceControl.Transaction, long, float, int, int, int, int)}. * </ul> - * * <p> * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of * this three {@link SurfaceControl}s which then delegates the animation to the @@ -520,35 +556,22 @@ class ScreenRotationAnimation { */ class SurfaceRotationAnimationController { private SurfaceAnimator mDisplayAnimator; + private SurfaceAnimator mEnterBlackFrameAnimator; private SurfaceAnimator mScreenshotRotationAnimator; private SurfaceAnimator mRotateScreenAnimator; - private SurfaceAnimator mEnterBlackFrameAnimator; - - void startCustomAnimation() { - try { - mService.mSurfaceAnimationRunner.deferStartingAnimations(); - mRotateScreenAnimator = startScreenshotAlphaAnimation(); - mDisplayAnimator = startDisplayRotation(); - if (mEnteringBlackFrame != null) { - mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); - } - } finally { - mService.mSurfaceAnimationRunner.continueStartingAnimations(); - } - } /** * Start the rotation animation of the display and the screenshot on the * {@link SurfaceAnimationRunner}. */ - void startScreenRotationAnimation() { - try { - mService.mSurfaceAnimationRunner.deferStartingAnimations(); - mDisplayAnimator = startDisplayRotation(); + void startAnimation() { + mRotateScreenAnimator = startScreenshotAlphaAnimation(); + mDisplayAnimator = startDisplayRotation(); + if (mExitingBlackFrame != null) { mScreenshotRotationAnimator = startScreenshotRotationAnimation(); - startColorAnimation(); - } finally { - mService.mSurfaceAnimationRunner.continueStartingAnimations(); + } + if (mEnteringBlackFrame != null) { + mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); } } @@ -573,8 +596,8 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotAlphaAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mScreenshotLayer) - .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) + .setSurfaceControl(mSurfaceControl) + .setAnimationLeashParent(mRotationLayer) .setWidth(mWidth) .setHeight(mHeight) .build(), @@ -593,67 +616,13 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotRotationAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mScreenshotLayer) + .setSurfaceControl(mRotationLayer) .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) .build(), createWindowAnimationSpec(mRotateExitAnimation), this::onAnimationEnd); } - - /** - * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a - * grayscale color - */ - private void startColorAnimation() { - int colorTransitionMs = mContext.getResources().getInteger( - R.integer.config_screen_rotation_color_transition); - final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner; - final float[] rgbTmpFloat = new float[3]; - final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma); - final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma); - final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale(); - final ArgbEvaluator va = ArgbEvaluator.getInstance(); - runner.startAnimation( - new LocalAnimationAdapter.AnimationSpec() { - @Override - public long getDuration() { - return duration; - } - - @Override - public void apply(SurfaceControl.Transaction t, SurfaceControl leash, - long currentPlayTime) { - float fraction = (float)currentPlayTime / (float)getDuration(); - int color = (Integer) va.evaluate(fraction, startColor, endColor); - Color middleColor = Color.valueOf(color); - rgbTmpFloat[0] = middleColor.red(); - rgbTmpFloat[1] = middleColor.green(); - rgbTmpFloat[2] = middleColor.blue(); - if (leash.isValid()) { - t.setColor(leash, rgbTmpFloat); - } - } - - @Override - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "startLuma=" + mStartLuma - + " endLuma=" + mEndLuma - + " durationMs=" + colorTransitionMs); - } - - @Override - public void dumpDebugInner(ProtoOutputStream proto) { - final long token = proto.start(ROTATE); - proto.write(START_LUMA, mStartLuma); - proto.write(END_LUMA, mEndLuma); - proto.write(DURATION_MS, colorTransitionMs); - proto.end(token); - } - }, - mBackColorSurface, mDisplayContent.getPendingTransaction(), null); - } - private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) { return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */, false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */); @@ -677,6 +646,7 @@ class ScreenRotationAnimation { LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter( animationSpec, mService.mSurfaceAnimationRunner); + animator.startAnimation(mDisplayContent.getPendingTransaction(), localAnimationAdapter, false); return animator; @@ -722,6 +692,7 @@ class ScreenRotationAnimation { if (mEnterBlackFrameAnimator != null) { mEnterBlackFrameAnimator.cancelAnimation(); } + if (mScreenshotRotationAnimator != null) { mScreenshotRotationAnimator.cancelAnimation(); } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 5633b6be87b9..50cea2ed52cc 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -78,10 +78,6 @@ class SurfaceAnimationRunner { @GuardedBy("mLock") private boolean mAnimationStartDeferred; - /** - * There should only ever be one instance of this class. Usual spot for it is with - * {@link WindowManagerService} - */ SurfaceAnimationRunner(Supplier<Transaction> transactionFactory, PowerManagerInternal powerManagerInternal) { this(null /* callbackProvider */, null /* animatorFactory */, diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9a140daad417..5cb7091bbed0 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -117,9 +117,11 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -130,6 +132,7 @@ import android.util.DisplayMetrics; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; @@ -425,6 +428,14 @@ class Task extends WindowContainer<WindowContainer> { } /** + * The TaskOrganizer which is delegated presentation of this task. If set the Task will + * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers + * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished. + */ + ITaskOrganizer mTaskOrganizer; + + + /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, * ActivityInfo, Intent, TaskDescription)} instead. */ @@ -445,6 +456,22 @@ class Task extends WindowContainer<WindowContainer> { _voiceSession, _voiceInteractor, stack); } + class TaskToken extends RemoteToken { + TaskToken(ConfigurationContainer container) { + super(container); + } + + @Override + public SurfaceControl getLeash() { + // We need to copy the SurfaceControl instead of returning the original + // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls + // to release themselves. + SurfaceControl sc = new SurfaceControl(); + sc.copyFrom(getSurfaceControl()); + return sc; + } + } + /** Don't use constructor directly. This is only used by XML parser. */ Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, @@ -469,7 +496,7 @@ class Task extends WindowContainer<WindowContainer> { mTaskDescription = _lastTaskDescription; // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED). setOrientation(SCREEN_ORIENTATION_UNSET); - mRemoteToken = new RemoteToken(this); + mRemoteToken = new TaskToken(this); affinityIntent = _affinityIntent; affinity = _affinity; rootAffinity = _rootAffinity; @@ -2179,6 +2206,10 @@ class Task extends WindowContainer<WindowContainer> { void removeImmediately() { if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId); EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask"); + + // If applicable let the TaskOrganizer know the Task is vanishing. + setTaskOrganizer(null); + super.removeImmediately(); } @@ -2567,6 +2598,12 @@ class Task extends WindowContainer<WindowContainer> { } boolean shouldAnimate() { + /** + * Animations are handled by the TaskOrganizer implementation. + */ + if (isControlledByTaskOrganizer()) { + return false; + } // Don't animate while the task runs recents animation but only if we are in the mode // where we cancel with deferred screenshot, which means that the controller has // transformed the task. @@ -3444,4 +3481,91 @@ class Task extends WindowContainer<WindowContainer> { XmlUtils.skipCurrentTag(in); } } + + boolean isControlledByTaskOrganizer() { + return mTaskOrganizer != null; + } + + @Override + protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) { + /** + * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the + * Surface in to its own hierarchy. + */ + if (isControlledByTaskOrganizer()) { + return; + } + super.reparentSurfaceControl(t, newParent); + } + + private void sendTaskAppeared() { + if (mSurfaceControl != null && mTaskOrganizer != null) { + mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this); + } + } + + private void sendTaskVanished() { + if (mTaskOrganizer != null) { + mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this); + } + } + + void setTaskOrganizer(ITaskOrganizer organizer) { + // Let the old organizer know it has lost control. + if (mTaskOrganizer != null) { + sendTaskVanished(); + } + mTaskOrganizer = organizer; + sendTaskAppeared(); + } + + // Called on Binder death. + void taskOrganizerDied() { + mTaskOrganizer = null; + } + + @Override + void setSurfaceControl(SurfaceControl sc) { + super.setSurfaceControl(sc); + // If the TaskOrganizer was set before we created the SurfaceControl, we need to + // emit the callbacks now. + sendTaskAppeared(); + } + + @Override + public void updateSurfacePosition() { + // Avoid fighting with the TaskOrganizer over Surface position. + if (isControlledByTaskOrganizer()) { + getPendingTransaction().setPosition(mSurfaceControl, 0, 0); + scheduleAnimation(); + return; + } else { + super.updateSurfacePosition(); + } + } + + @Override + void getRelativeDisplayedPosition(Point outPos) { + // In addition to updateSurfacePosition, we keep other code that sets + // position from fighting with the TaskOrganizer + if (isControlledByTaskOrganizer()) { + outPos.set(0, 0); + return; + } + super.getRelativeDisplayedPosition(outPos); + } + + @Override + public void setWindowingMode(int windowingMode) { + super.setWindowingMode(windowingMode); + windowingMode = getWindowingMode(); + /* + * Different windowing modes may be managed by different task organizers. If + * getTaskOrganizer returns null, we still call transferToTaskOrganizer to + * make sure we clear it. + */ + final ITaskOrganizer org = + mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + setTaskOrganizer(org); + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java new file mode 100644 index 000000000000..283be4010677 --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Stores the TaskOrganizers associated with a given windowing mode and + * their associated state. + */ +class TaskOrganizerController { + private static final String TAG = "TaskOrganizerController"; + + private WindowManagerGlobalLock mGlobalLock; + + private class DeathRecipient implements IBinder.DeathRecipient { + int mWindowingMode; + ITaskOrganizer mTaskOrganizer; + + DeathRecipient(ITaskOrganizer organizer, int windowingMode) { + mTaskOrganizer = organizer; + mWindowingMode = windowingMode; + } + + @Override + public void binderDied() { + synchronized (mGlobalLock) { + final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer); + for (int i = 0; i < state.mOrganizedTasks.size(); i++) { + state.mOrganizedTasks.get(i).taskOrganizerDied(); + } + mTaskOrganizerStates.remove(mTaskOrganizer); + if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) { + mTaskOrganizersForWindowingMode.remove(mWindowingMode); + } + } + } + }; + + class TaskOrganizerState { + ITaskOrganizer mOrganizer; + DeathRecipient mDeathRecipient; + + ArrayList<Task> mOrganizedTasks = new ArrayList<>(); + + void addTask(Task t) { + mOrganizedTasks.add(t); + } + + void removeTask(Task t) { + mOrganizedTasks.remove(t); + } + + TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) { + mOrganizer = organizer; + mDeathRecipient = deathRecipient; + } + }; + + + final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap(); + final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap(); + + final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap(); + + final ActivityTaskManagerService mService; + + TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) { + mService = atm; + mGlobalLock = lock; + } + + private void clearIfNeeded(int windowingMode) { + final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode); + if (oldState != null) { + oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0); + } + } + + /** + * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. + * If there was already a TaskOrganizer for this windowing mode it will be evicted + * and receive taskVanished callbacks in the process. + */ + void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + if (windowingMode != WINDOWING_MODE_PINNED) { + throw new UnsupportedOperationException( + "As of now only Pinned windowing mode is supported for registerTaskOrganizer"); + + } + clearIfNeeded(windowingMode); + DeathRecipient dr = new DeathRecipient(organizer, windowingMode); + try { + organizer.asBinder().linkToDeath(dr, 0); + } catch (RemoteException e) { + Slog.e(TAG, "TaskOrganizer failed to register death recipient"); + } + + final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); + mTaskOrganizersForWindowingMode.put(windowingMode, state); + + mTaskOrganizerStates.put(organizer, state); + } + + ITaskOrganizer getTaskOrganizer(int windowingMode) { + final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode); + if (state == null) { + return null; + } + return state.mOrganizer; + } + + private void sendTaskAppeared(ITaskOrganizer organizer, Task task) { + try { + organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskAppeared callback" + e); + } + } + + private void sendTaskVanished(ITaskOrganizer organizer, Task task) { + try { + organizer.taskVanished(task.getRemoteToken()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskVanished callback" + e); + } + } + + void onTaskAppeared(ITaskOrganizer organizer, Task task) { + TaskOrganizerState state = mTaskOrganizerStates.get(organizer); + + state.addTask(task); + sendTaskAppeared(organizer, task); + } + + void onTaskVanished(ITaskOrganizer organizer, Task task) { + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer); + sendTaskVanished(organizer, task); + + // This could trigger TaskAppeared for other tasks in the same stack so make sure + // we do this AFTER sending taskVanished. + state.removeTask(task); + } +} diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8ac212f7ef03..c38868a2af84 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -42,6 +42,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; 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.logWithStack; +import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import android.annotation.CallSuper; @@ -342,7 +343,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mSurfaceControl == null) { // If we don't yet have a surface, but we now have a parent, we should // build a surface. - mSurfaceControl = makeSurface().build(); + setSurfaceControl(makeSurface().build()); getPendingTransaction().show(mSurfaceControl); updateSurfacePosition(); } else { @@ -496,7 +497,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mParent.getPendingTransaction().merge(getPendingTransaction()); } - mSurfaceControl = null; + setSurfaceControl(null); mLastSurfacePosition.set(0, 0); scheduleAnimation(); } @@ -1855,7 +1856,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // TODO: Remove this and use #getBounds() instead once we set an app transition animation // on TaskStack. Rect getAnimationBounds(int appStackClipMode) { - return getBounds(); + return getDisplayedBounds(); } /** @@ -1929,7 +1930,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // Separate position and size for use in animators. mTmpRect.set(getAnimationBounds(appStackClipMode)); - mTmpPoint.set(mTmpRect.left, mTmpRect.top); + if (sHierarchicalAnimations) { + getRelativeDisplayedPosition(mTmpPoint); + } else { + mTmpPoint.set(mTmpRect.left, mTmpRect.top); + } mTmpRect.offsetTo(0, 0); final RemoteAnimationController controller = @@ -2209,4 +2214,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } return mParent.getDimmer(); } + + void setSurfaceControl(SurfaceControl sc) { + mSurfaceControl = sc; + } } diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java deleted file mode 100644 index 94f66768d5ef..000000000000 --- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.utils; - -import android.graphics.Bitmap; -import android.graphics.ColorSpace; -import android.graphics.GraphicBuffer; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.Rect; -import android.view.Display; -import android.view.Surface; -import android.view.SurfaceControl; - - -/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/ -public class RotationAnimationUtils { - - /** - * Converts the provided {@link GraphicBuffer} and converts it to a bitmap to then sample the - * luminance at the borders of the bitmap - * @return the average luminance of all the pixels at the borders of the bitmap - */ - public static float getAvgBorderLuma(GraphicBuffer graphicBuffer, ColorSpace colorSpace) { - Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(graphicBuffer, colorSpace); - if (hwBitmap == null) { - return 0; - } - - Bitmap swaBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false); - float totalLuma = 0; - int height = swaBitmap.getHeight(); - int width = swaBitmap.getWidth(); - int i; - for (i = 0; i < width; i++) { - totalLuma += swaBitmap.getColor(i, 0).luminance(); - totalLuma += swaBitmap.getColor(i, height - 1).luminance(); - } - for (i = 0; i < height; i++) { - totalLuma += swaBitmap.getColor(0, i).luminance(); - totalLuma += swaBitmap.getColor(width - 1, i).luminance(); - } - return totalLuma / (2 * width + 2 * height); - } - - /** - * Gets the average border luma by taking a screenshot of the {@param surfaceControl}. - * @see #getAvgBorderLuma(GraphicBuffer, ColorSpace) - */ - public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) { - if (surfaceControl == null) { - return 0; - } - - Point size = new Point(); - display.getSize(size); - Rect crop = new Rect(0, 0, size.x, size.y); - SurfaceControl.ScreenshotGraphicBuffer buffer = - SurfaceControl.captureLayers(surfaceControl, crop, 1); - return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(), - buffer.getColorSpace()); - } - - public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) { - switch (rotation) { - case Surface.ROTATION_0: - outMatrix.reset(); - break; - case Surface.ROTATION_90: - outMatrix.setRotate(90, 0, 0); - outMatrix.postTranslate(height, 0); - break; - case Surface.ROTATION_180: - outMatrix.setRotate(180, 0, 0); - outMatrix.postTranslate(width, height); - break; - case Surface.ROTATION_270: - outMatrix.setRotate(270, 0, 0); - outMatrix.postTranslate(0, width); - break; - } - } -} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 1ad6e86bae84..03969b01f3f9 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -13,6 +13,7 @@ cc_library_static { ], srcs: [ + ":graphicsstats_proto", "BroadcastRadio/JavaRef.cpp", "BroadcastRadio/NativeCallbackThread.cpp", "BroadcastRadio/BroadcastRadioService.cpp", @@ -103,6 +104,11 @@ cc_defaults { "libinputflinger", "libinputflinger_base", "libinputservice", + "libprotobuf-cpp-lite", + "libprotoutil", + "libstatspull", + "libstatssocket", + "libstatslog", "libschedulerservicehidl", "libsensorservice", "libsensorservicehidl", diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp index d1d253b94713..9353fbd71214 100644 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -23,6 +23,16 @@ #include <nativehelper/ScopedUtfChars.h> #include <JankTracker.h> #include <service/GraphicsStatsService.h> +#include <stats_pull_atom_callback.h> +#include <stats_event.h> +#include <statslog.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> +#include <android/util/ProtoOutputStream.h> +#include "android/graphics/Utils.h" +#include "core_jni_helpers.h" +#include "protos/graphicsstats.pb.h" +#include <cstring> +#include <memory> namespace android { @@ -77,6 +87,20 @@ static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { GraphicsStatsService::finishDump(dump); } +static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + std::vector<uint8_t>* result = new std::vector<uint8_t>(); + GraphicsStatsService::finishDumpInMemory(dump, + [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) { + std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2); + if (outBuffer->size() < totalSize) { + outBuffer->resize(totalSize); + } + std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize); + }, env, result); + return reinterpret_cast<jlong>(result); +} + static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { ScopedByteArrayRO buffer(env, jdata); @@ -93,19 +117,173 @@ static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpacka GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); } +static jobject gGraphicsStatsServiceObject = nullptr; +static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; + +static JNIEnv* getJNIEnv() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + JNIEnv* env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +using namespace google::protobuf; + +// Field ids taken from FrameTimingHistogram message in atoms.proto +#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 +#define FRAME_COUNTS_FIELD_NUMBER 2 + +static void writeCpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +static void writeGpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. +static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data, + const void* cookie) { + JNIEnv* env = getJNIEnv(); + if (!env) { + return false; + } + if (gGraphicsStatsServiceObject == nullptr) { + ALOGE("Failed to get graphicsstats service"); + return false; + } + + for (bool lastFullDay : {true, false}) { + jlong jdata = (jlong) env->CallLongMethod( + gGraphicsStatsServiceObject, + gGraphicsStatsService_pullGraphicsStatsMethodID, + (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + ALOGE("Failed to invoke graphicsstats service"); + return false; + } + if (!jdata) { + // null means data is not available for that day. + continue; + } + android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump; + std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata); + std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer); + int dataSize = buffer->size(); + if (!dataSize) { + // Data is not available for that day. + continue; + } + io::ArrayInputStream input{buffer->data(), dataSize}; + bool success = serviceDump.ParseFromZeroCopyStream(&input); + if (!success) { + ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", + serviceDump.InitializationErrorString().c_str(), dataSize); + return false; + } + + for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { + auto& stat = serviceDump.stats(stat_index); + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, android::util::GRAPHICS_STATS); + stats_event_write_string8(event, stat.package_name().c_str()); + stats_event_write_int64(event, (int64_t)stat.version_code()); + stats_event_write_int64(event, (int64_t)stat.stats_start()); + stats_event_write_int64(event, (int64_t)stat.stats_end()); + stats_event_write_int32(event, (int32_t)stat.pipeline()); + stats_event_write_int32(event, (int32_t)stat.summary().total_frames()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count()); + stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count()); + writeCpuHistogram(event, stat); + writeGpuHistogram(event, stat); + // TODO: fill in UI mainline module version, when the feature is available. + stats_event_write_int64(event, (int64_t)0); + stats_event_write_bool(event, !lastFullDay); + stats_event_build(event); + } + } + return true; +} + +// Register a puller for GRAPHICS_STATS atom with the statsd service. +static void nativeInit(JNIEnv* env, jobject javaObject) { + gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); + pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds + .timeout_ns = 2 * NS_PER_SEC, // 2 seconds + .additive_fields = nullptr, + .additive_fields_size = 0}; + register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback, + &metadata, nullptr); +} + +static void nativeDestructor(JNIEnv* env, jobject javaObject) { + //TODO: Unregister the puller callback when a new API is available. + env->DeleteGlobalRef(gGraphicsStatsServiceObject); + gGraphicsStatsServiceObject = nullptr; +} + static const JNINativeMethod sMethods[] = { { "nGetAshmemSize", "()I", (void*) getAshmemSize }, { "nCreateDump", "(IZ)J", (void*) createDump }, { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump }, { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump }, { "nFinishDump", "(J)V", (void*) finishDump }, + { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory }, { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer }, + { "nativeInit", "()V", (void*) nativeInit }, + { "nativeDestructor", "()V", (void*)nativeDestructor } }; int register_android_server_GraphicsStatsService(JNIEnv* env) { + jclass graphicsStatsService_class = FindClassOrDie(env, + "com/android/server/GraphicsStatsService"); + gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env, + graphicsStatsService_class, "pullGraphicsStats", "(Z)J"); return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService", sMethods, NELEM(sMethods)); } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 6504e3188a0d..00436bb8ca70 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -129,7 +129,6 @@ using android::hardware::hidl_vec; using android::hardware::hidl_string; using android::hardware::hidl_death_recipient; -using android::hardware::gnss::V1_0::GnssConstellationType; using android::hardware::gnss::V1_0::GnssLocationFlags; using android::hardware::gnss::V1_0::IAGnssRilCallback; using android::hardware::gnss::V1_0::IGnssGeofenceCallback; @@ -149,6 +148,8 @@ using android::hardware::gnss::measurement_corrections::V1_0::ReflectingPlane; using android::hidl::base::V1_0::IBase; +using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType; +using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType; using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation; using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation; using IGnss_V1_0 = android::hardware::gnss::V1_0::IGnss; @@ -161,6 +162,7 @@ using IGnssCallback_V2_1 = android::hardware::gnss::V2_1::IGnssCallback; using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration; using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration; using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration; +using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration; using IGnssDebug_V1_0 = android::hardware::gnss::V1_0::IGnssDebug; using IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug; using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement; @@ -202,6 +204,7 @@ struct GnssDeathRecipient : virtual public hidl_death_recipient // Must match the value from GnssMeasurement.java static const uint32_t ADR_STATE_HALF_CYCLE_REPORTED = (1<<4); +static const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1<<4); sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr; sp<IGnss_V1_0> gnssHal = nullptr; @@ -221,6 +224,7 @@ sp<IGnssDebug_V2_0> gnssDebugIface_V2_0 = nullptr; sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr; sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr; sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr; +sp<IGnssConfiguration_V2_1> gnssConfigurationIface_V2_1 = nullptr; sp<IGnssNi> gnssNiIface = nullptr; sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr; sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr; @@ -631,6 +635,16 @@ private: template<class T> Return<void> gnssSvStatusCbImpl(const T& svStatus); + template<class T> + uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) { + return 0; + } + + template<class T> + double getBasebandCn0DbHz(const T& svStatus, size_t i) { + return 0.0; + } + uint32_t getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) { return svStatus.numSvs; } @@ -655,8 +669,6 @@ private: const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) { - // TODO(b/144850155): fill baseband CN0 after it's available in Java object. - ALOGD("getGnssSvInfoOfIndex %d: baseband C/N0: %f", (int) i, svInfoList[i].basebandCN0DbHz); return svInfoList[i].v2_0.v1_0; } @@ -718,6 +730,18 @@ Return<void> GnssCallback::gnssStatusCb(const IGnssCallback_V2_0::GnssStatusValu return Void(); } +template<> +uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& + svStatus) { + return SVID_FLAGS_HAS_BASEBAND_CN0; +} + +template<> +double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, + size_t i) { + return svInfoList[i].basebandCN0DbHz; +} + template<class T> Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { JNIEnv* env = getJniEnv(); @@ -755,8 +779,8 @@ Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { elev[i] = info.elevationDegrees; azim[i] = info.azimuthDegrees; carrierFreq[i] = info.carrierFrequencyHz; - // TODO(b/144850155): fill svidWithFlags with hasBasebandCn0DbHz based on HAL versions - basebandCn0s[i] = 0.0; + svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus); + basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i); } env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0); @@ -1182,8 +1206,8 @@ void GnssMeasurementCallback::translateSingleGnssMeasurement const IGnssMeasurementCallback_V2_1::GnssMeasurement* measurement_V2_1, JavaObject& object) { translateSingleGnssMeasurement(&(measurement_V2_1->v2_0), object); - // TODO(b/144850155): fill baseband CN0 after it's available in Java object - ALOGD("baseband CN0DbHz = %f\n", measurement_V2_1->basebandCN0DbHz); + + SET(BasebandCn0DbHz, measurement_V2_1->basebandCN0DbHz); } template<class T> @@ -1888,7 +1912,17 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass gnssNiIface = gnssNi; } - if (gnssHal_V2_0 != nullptr) { + if (gnssHal_V2_1 != nullptr) { + auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1(); + if (!gnssConfiguration.isOk()) { + ALOGD("Unable to get a handle to GnssConfiguration_V2_1"); + } else { + gnssConfigurationIface_V2_1 = gnssConfiguration; + gnssConfigurationIface_V2_0 = gnssConfigurationIface_V2_1; + gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_1; + gnssConfigurationIface = gnssConfigurationIface_V2_1; + } + } else if (gnssHal_V2_0 != nullptr) { auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0(); if (!gnssConfiguration.isOk()) { ALOGD("Unable to get a handle to GnssConfiguration_V2_0"); @@ -1962,7 +1996,11 @@ static jboolean android_location_GnssNetworkConnectivityHandler_is_agps_ril_supp static jobject android_location_GnssConfiguration_get_gnss_configuration_version( JNIEnv* env, jclass /* jclazz */) { jint major, minor; - if (gnssConfigurationIface_V2_0 != nullptr) { + if (gnssConfigurationIface_V2_1 != nullptr) { + major = 2; + minor = 1; + } + else if (gnssConfigurationIface_V2_0 != nullptr) { major = 2; minor = 0; } else if (gnssConfigurationIface_V1_1 != nullptr) { @@ -2768,7 +2806,7 @@ static jboolean SingleSatCorrection singleSatCorrection = { .singleSatCorrectionFlags = corrFlags, - .constellation = static_cast<GnssConstellationType>(constType), + .constellation = static_cast<GnssConstellationType_V1_0>(constType), .svid = static_cast<uint16_t>(satId), .carrierFrequencyHz = carrierFreqHz, .probSatIsLos = probSatIsLos, @@ -2863,8 +2901,8 @@ static jboolean android_location_GnssConfiguration_set_supl_version(JNIEnv*, static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*, jobject, jint suplEs) { - if (gnssConfigurationIface_V2_0 != nullptr) { - ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0."); + if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) { + ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and higher."); return JNI_FALSE; } @@ -2892,7 +2930,7 @@ static jboolean android_location_GnssConfiguration_set_supl_mode(JNIEnv*, static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*, jobject, jint gpsLock) { - if (gnssConfigurationIface_V2_0 != nullptr) { + if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) { ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0."); return JNI_FALSE; } @@ -2932,7 +2970,7 @@ static jboolean android_location_GnssConfiguration_set_gnss_pos_protocol_select( static jboolean android_location_GnssConfiguration_set_satellite_blacklist( JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) { - if (gnssConfigurationIface_V1_1 == nullptr) { + if (gnssConfigurationIface_V1_1 == nullptr && gnssConfigurationIface_V2_1 == nullptr) { ALOGI("IGnssConfiguration interface does not support satellite blacklist."); return JNI_FALSE; } @@ -2955,11 +2993,24 @@ static jboolean android_location_GnssConfiguration_set_satellite_blacklist( return JNI_FALSE; } + if (gnssConfigurationIface_V2_1 != nullptr) { + hidl_vec<IGnssConfiguration_V2_1::BlacklistedSource> sources; + sources.resize(length); + + for (int i = 0; i < length; i++) { + sources[i].constellation = static_cast<GnssConstellationType_V2_0>(constellation_array[i]); + sources[i].svid = sv_id_array[i]; + } + + auto result = gnssConfigurationIface_V2_1->setBlacklist_2_1(sources); + return checkHidlReturn(result, "IGnssConfiguration_V2_1 setBlacklist_2_1() failed."); + } + hidl_vec<IGnssConfiguration_V1_1::BlacklistedSource> sources; sources.resize(length); for (int i = 0; i < length; i++) { - sources[i].constellation = static_cast<GnssConstellationType>(constellation_array[i]); + sources[i].constellation = static_cast<GnssConstellationType_V1_0>(constellation_array[i]); sources[i].svid = sv_id_array[i]; } diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp index 4696dd0bb88b..0275f3ea32f7 100644 --- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp +++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp @@ -33,7 +33,6 @@ #include "bpf/BpfUtils.h" #include "netdbpf/BpfNetworkStats.h" -using android::bpf::Stats; using android::bpf::bpfGetUidStats; using android::bpf::bpfGetIfaceStats; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d5ff2802499c..485899ef05e3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -23,7 +23,6 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; -import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER; @@ -129,6 +128,7 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DeviceStateCache; +import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; @@ -268,6 +268,7 @@ import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.PasswordValidationError; import com.android.server.LocalServices; import com.android.server.LockGuard; +import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; @@ -1006,6 +1007,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL = "cross-profile-calendar-packages-null"; private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages"; + private static final String TAG_FACTORY_RESET_PROTECTION_POLICY = + "factory_reset_protection_policy"; DeviceAdminInfo info; @@ -1016,6 +1019,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @NonNull PasswordPolicy mPasswordPolicy = new PasswordPolicy(); + @Nullable + FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null; + static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0; long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK; @@ -1351,6 +1357,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mCrossProfileCalendarPackages); } writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages); + if (mFactoryResetProtectionPolicy != null) { + out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + mFactoryResetProtectionPolicy.writeToXml(out); + out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + } } void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException { @@ -1584,6 +1595,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mCrossProfileCalendarPackages = null; } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) { mCrossProfilePackages = readPackageList(parser, tag); + } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) { + mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml( + parser); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -2036,6 +2050,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE)); } + PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() { + return LocalServices.getService(PersistentDataBlockManagerInternal.class); + } + LockSettingsInternal getLockSettingsInternal() { return LocalServices.getService(LockSettingsInternal.class); } @@ -4099,6 +4117,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle); } + // When a device owner is set, the system automatically restricts adding a managed profile. + // Remove this restriction when the device owner is cleared. + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, userHandle)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, + userHandle); + } } /** @@ -6736,6 +6760,67 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void setFactoryResetProtectionPolicy(ComponentName who, + @Nullable FactoryResetProtectionPolicy policy) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + + final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); + final int userId = mInjector.userHandleGetCallingUserId(); + synchronized (getLockObject()) { + ActiveAdmin admin = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + admin.mFactoryResetProtectionPolicy = policy; + saveSettingsLocked(userId); + } + + mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser( + new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + UserHandle.getUserHandleForUid(frpManagementAgentUid))); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION) + .setAdmin(who) + .write(); + } + + @Override + public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy( + @Nullable ComponentName who) { + if (!mHasFeature) { + return null; + } + + final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); + ActiveAdmin admin; + synchronized (getLockObject()) { + if (who == null) { + if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) { + throw new SecurityException( + "Must be called by the FRP management agent on device"); + } + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.getUserId(frpManagementAgentUid)); + } else { + admin = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + } + } + return admin != null ? admin.mFactoryResetProtectionPolicy : null; + } + + private int getFrpManagementAgentUidOrThrow() { + PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal(); + if ((pdb == null) || (pdb.getAllowedUid() == -1)) { + throw new UnsupportedOperationException( + "The persistent data block service is not supported on this device"); + } + return pdb.getAllowedUid(); + } + + @Override public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) { if (!mHasFeature) { return; @@ -7976,10 +8061,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateDeviceOwnerLocked(); setDeviceOwnerSystemPropertyLocked(); - // TODO Send to system too? - mInjector.binderWithCleanCallingIdentity( - () -> sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, - userId)); + mInjector.binderWithCleanCallingIdentity(() -> { + // Restrict adding a managed profile when a device owner is set on the device. + // That is to prevent the co-existence of a managed profile and a device owner + // on the same device. + // Instead, the device may be provisioned with an organization-owned managed + // profile, such that the admin on that managed profile has extended management + // capabilities that can affect the entire device (but not access private data + // on the primary profile). + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, + UserHandle.of(userId)); + // TODO Send to system too? + sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); + }); mDeviceAdminServiceController.startServiceForOwner( admin.getPackageName(), userId, "set-device-owner"); @@ -8131,6 +8225,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } + ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) { + ActiveAdmin admin = getDeviceOwnerAdminLocked(); + if (admin == null) { + admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId); + } + return admin; + } + @Override public void clearDeviceOwner(String packageName) { Objects.requireNonNull(packageName, "packageName is null"); @@ -8234,6 +8336,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalArgumentException("Not active admin: " + who); } + final int parentUserId = getProfileParentId(userHandle); + // When trying to set a profile owner on a new user, it may be that this user is + // a profile - but it may not be a managed profile if there's a restriction on the + // parent to add managed profiles (e.g. if the device has a device owner). + if (parentUserId != userHandle && mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, + UserHandle.of(parentUserId))) { + Slog.i(LOG_TAG, "Cannot set profile owner because of restriction."); + return false; + } + if (isAdb()) { // Log profile owner provisioning was started using adb. MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER); @@ -12212,6 +12325,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE: return checkManagedProfileProvisioningPreCondition(packageName, callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE: + case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE: return checkDeviceOwnerProvisioningPreCondition(callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER: return checkManagedUserProvisioningPreCondition(callingUserId); @@ -12298,25 +12412,41 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long ident = mInjector.binderClearCallingIdentity(); try { final UserHandle callingUserHandle = UserHandle.of(callingUserId); - final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId); - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - callingUserHandle)) { - // An admin can initiate provisioning if it has set the restriction. - if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin, - UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) { - return CODE_ADD_MANAGED_PROFILE_DISALLOWED; - } - } - boolean canRemoveProfile = true; - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, - callingUserHandle)) { - // We can remove a profile if the admin itself has set the restriction. - if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin, - UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, - callingUserId)) { - canRemoveProfile = false; - } + final boolean hasDeviceOwner; + synchronized (getLockObject()) { + hasDeviceOwner = getDeviceOwnerAdminLocked() != null; + } + + final boolean addingProfileRestricted = mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle); + + UserInfo parentUser = mUserManager.getProfileParent(callingUserId); + final boolean addingProfileRestrictedOnParent = (parentUser != null) + && mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, + UserHandle.of(parentUser.id)); + + Slog.i(LOG_TAG, String.format( + "When checking for managed profile provisioning: Has device owner? %b, adding" + + " profile restricted? %b, adding profile restricted on parent? %b", + hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent)); + + // If there's a device owner, the restriction on adding a managed profile must be set + // somewhere. + if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) { + Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile."); + } + + // Do not allow adding a managed profile if there's a restriction, either on the current + // user or its parent user. + if (addingProfileRestricted || addingProfileRestrictedOnParent) { + return CODE_CANNOT_ADD_MANAGED_PROFILE; } + // If there's a restriction on removing the managed profile then we have to take it + // into account when checking whether more profiles can be added. + boolean canRemoveProfile = + !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + callingUserHandle); if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { return CODE_CANNOT_ADD_MANAGED_PROFILE; } diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java index a8a258fc5ff7..9c5d4ad6ceeb 100644 --- a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java +++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java @@ -3,6 +3,7 @@ package com.android.server.location; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import android.os.Looper; import android.os.SystemClock; @@ -52,8 +53,10 @@ public class NtpTimeHelperTest { @Test public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException { - doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge(); - doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime(); + NtpTrustedTime.TimeResult result = mock(NtpTrustedTime.TimeResult.class); + doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(result).getAgeMillis(); + doReturn(MOCK_NTP_TIME).when(result).getTimeMillis(); + doReturn(result).when(mMockNtpTrustedTime).getCachedTimeResult(); mNtpTimeHelper.retrieveAndInjectNtpTime(); @@ -64,7 +67,9 @@ public class NtpTimeHelperTest { @Test public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed() throws InterruptedException { - doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge(); + NtpTrustedTime.TimeResult result1 = mock(NtpTrustedTime.TimeResult.class); + doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(result1).getAgeMillis(); + doReturn(result1).when(mMockNtpTrustedTime).getCachedTimeResult(); doReturn(false).when(mMockNtpTrustedTime).forceRefresh(); mNtpTimeHelper.retrieveAndInjectNtpTime(); @@ -72,8 +77,10 @@ public class NtpTimeHelperTest { assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse(); doReturn(true).when(mMockNtpTrustedTime).forceRefresh(); - doReturn(1L).when(mMockNtpTrustedTime).getCacheAge(); - doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime(); + NtpTrustedTime.TimeResult result2 = mock(NtpTrustedTime.TimeResult.class); + doReturn(1L).when(result2).getAgeMillis(); + doReturn(MOCK_NTP_TIME).when(result2).getTimeMillis(); + doReturn(result2).when(mMockNtpTrustedTime).getCachedTimeResult(); SystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL); waitForTasksToBePostedOnHandlerAndRunThem(); diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index 556f96ace5d2..6a5de84266e2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -345,8 +345,8 @@ public class AlarmManagerServiceTest { } /** - * Lowers quotas to make testing feasible. - * Careful while calling as this will replace any existing settings for the calling test. + * Lowers quotas to make testing feasible. Careful while calling as this will replace any + * existing settings for the calling test. */ private void setTestableQuotas() { final StringBuilder constantsBuilder = new StringBuilder(); @@ -981,6 +981,25 @@ public class AlarmManagerServiceTest { assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } + @Test + public void alarmCountOnListenerBinderDied() { + final int numAlarms = 10; + final IAlarmListener[] listeners = new IAlarmListener[numAlarms]; + for (int i = 0; i < numAlarms; i++) { + listeners[i] = new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }; + setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]); + } + assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + for (int i = 0; i < numAlarms; i++) { + mService.mListenerDeathRecipient.binderDied(listeners[i].asBinder()); + assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + } + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java new file mode 100644 index 000000000000..06fb10257a37 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java @@ -0,0 +1,177 @@ +/* + * 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.location; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +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.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; +import android.os.Handler; +import android.os.UserHandle; +import android.os.UserManager; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.dx.mockito.inline.extended.StaticMockitoSession; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.quality.Strictness; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class UserInfoStoreTest { + + private static final int USER1_ID = 1; + private static final int USER1_MANAGED_ID = 11; + private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID}; + private static final int USER2_ID = 2; + private static final int USER2_MANAGED_ID = 12; + private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID}; + + @Mock private Context mContext; + @Mock private UserManager mUserManager; + + private StaticMockitoSession mMockingSession; + private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>(); + + private UserInfoStore mStore; + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .spyStatic(ActivityManager.class) + .strictness(Strictness.WARN) + .startMocking(); + + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); + doAnswer(invocation -> { + mBroadcastReceivers.add(invocation.getArgument(0)); + return null; + }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any( + UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class)); + doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID); + doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID); + doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent( + USER1_MANAGED_ID); + doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent( + USER2_MANAGED_ID); + + doReturn(USER1_ID).when(ActivityManager::getCurrentUser); + + mStore = new UserInfoStore(mContext); + mStore.onSystemReady(); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private void switchUser(int userId) { + doReturn(userId).when(ActivityManager::getCurrentUser); + Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, + userId); + for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) { + broadcastReceiver.onReceive(mContext, intent); + } + } + + @Test + public void testListeners() { + UserInfoStore.UserChangedListener listener = mock(UserInfoStore.UserChangedListener.class); + mStore.addListener(listener); + + switchUser(USER1_ID); + verify(listener, never()).onUserChanged(anyInt(), anyInt()); + + switchUser(USER2_ID); + verify(listener).onUserChanged(USER1_ID, USER2_ID); + + switchUser(USER1_ID); + verify(listener).onUserChanged(USER2_ID, USER1_ID); + } + + @Test + public void testCurrentUser() { + assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID); + + switchUser(USER2_ID); + + assertThat(mStore.getCurrentUserId()).isEqualTo(USER2_ID); + + switchUser(USER1_ID); + + assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID); + } + + @Test + public void testIsCurrentUserOrProfile() { + assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isTrue(); + assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isTrue(); + assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isFalse(); + assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isFalse(); + + switchUser(USER2_ID); + + assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isFalse(); + assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isTrue(); + assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isFalse(); + assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isTrue(); + } + + @Test + public void testGetParentUserId() { + assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID); + assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID); + assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID); + assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID); + + switchUser(USER2_ID); + + assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID); + assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID); + assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID); + assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID); + } +} diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 1829fb79699f..e609adc2a067 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -16,7 +16,7 @@ package com.android.server; -import static android.net.NetworkScoreManager.CACHE_FILTER_NONE; +import static android.net.NetworkScoreManager.SCORE_FILTER_NONE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -29,6 +29,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -56,7 +57,6 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiSsid; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -182,8 +182,8 @@ public class NetworkScoreServiceTest { } private ScanResult createScanResult(String ssid, String bssid) { - ScanResult result = new ScanResult(); - result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid); + ScanResult result = mock(ScanResult.class); + result.SSID = ssid; result.BSSID = bssid; return result; } @@ -306,7 +306,7 @@ public class NetworkScoreServiceTest { bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, - mNetworkScoreCache, CACHE_FILTER_NONE); + mNetworkScoreCache, SCORE_FILTER_NONE); mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); @@ -321,9 +321,9 @@ public class NetworkScoreServiceTest { bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, - mNetworkScoreCache, CACHE_FILTER_NONE); + mNetworkScoreCache, SCORE_FILTER_NONE); mNetworkScoreService.registerNetworkScoreCache( - NetworkKey.TYPE_WIFI, mNetworkScoreCache2, CACHE_FILTER_NONE); + NetworkKey.TYPE_WIFI, mNetworkScoreCache2, SCORE_FILTER_NONE); // updateScores should update both caches mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); @@ -378,7 +378,7 @@ public class NetworkScoreServiceTest { bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); + SCORE_FILTER_NONE); mNetworkScoreService.clearScores(); verify(mNetworkScoreCache).clearScores(); @@ -392,7 +392,7 @@ public class NetworkScoreServiceTest { .thenReturn(PackageManager.PERMISSION_GRANTED); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); + SCORE_FILTER_NONE); mNetworkScoreService.clearScores(); verify(mNetworkScoreCache).clearScores(); @@ -472,7 +472,7 @@ public class NetworkScoreServiceTest { try { mNetworkScoreService.registerNetworkScoreCache( - NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE); + NetworkKey.TYPE_WIFI, mNetworkScoreCache, SCORE_FILTER_NONE); fail("SecurityException expected"); } catch (SecurityException e) { // expected @@ -615,7 +615,7 @@ public class NetworkScoreServiceTest { new ArrayList<>(scoredNetworkList), NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE); verify(mNetworkScoreCache).updateScores(scoredNetworkList); verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); @@ -656,7 +656,7 @@ public class NetworkScoreServiceTest { Collections.emptyList(), NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE); verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter); } @@ -673,7 +673,7 @@ public class NetworkScoreServiceTest { List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList); filteredList.remove(SCORED_NETWORK); when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); verify(mNetworkScoreCache).updateScores(filteredList); verifyZeroInteractions(mScanResultsFilter); @@ -691,7 +691,7 @@ public class NetworkScoreServiceTest { List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList); filteredList.remove(SCORED_NETWORK); when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); verify(mNetworkScoreCache).updateScores(filteredList); verifyZeroInteractions(mCurrentNetworkFilter); @@ -794,7 +794,7 @@ public class NetworkScoreServiceTest { @Test public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception { List<ScanResult> invalidScanResults = Lists.newArrayList( - new ScanResult(), + mock(ScanResult.class), createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid), createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid), createScanResult(SSID, null), 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 591c3a385e23..c223f135d3df 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -804,6 +804,11 @@ public class AbstractAccessibilityServiceConnectionTest { } @Override + public boolean switchToInputMethod(String imeId) { + return false; + } + + @Override public boolean isAccessibilityButtonAvailable() throws RemoteException { return false; } diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 0f11566f512c..39a3aae767ea 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -212,7 +212,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(6, accounts.length); assertEquals(a11, accounts[0]); @@ -222,8 +223,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertEquals(a22, accounts[4]); assertEquals(a32, accounts[5]); - accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, - mContext.getOpPackageName()); + accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(3, accounts.length); assertEquals(a11, accounts[0]); @@ -232,8 +233,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { mAms.removeAccountInternal(a21); - accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, - mContext.getOpPackageName()); + accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(2, accounts.length); assertEquals(a11, accounts[0]); @@ -373,7 +374,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { unlockSystemUser(); String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, UserHandle.getCallingUserId(), + mContext.getOpPackageName()); assertEquals("1 account should be migrated", 1, accounts.length); assertEquals(PreNTestDatabaseHelper.ACCOUNT_NAME, accounts[0].name); assertEquals(PreNTestDatabaseHelper.ACCOUNT_PASSWORD, mAms.getPassword(accounts[0])); @@ -2980,7 +2982,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { Log.d(TAG, logPrefix + " getAccounts started"); long ti = System.currentTimeMillis(); try { - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); if (accounts == null || accounts.length != 1 || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals( accounts[0].type)) { @@ -3051,7 +3054,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { Log.d(TAG, logPrefix + " getAccounts started"); long ti = System.currentTimeMillis(); try { - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); if (accounts == null || accounts.length != 1 || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals( accounts[0].type)) { diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index d44476ed971d..3de006cea15f 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -57,7 +57,6 @@ import com.android.server.backup.utils.RandomAccessFileUtils; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; @@ -523,7 +522,6 @@ public class BackupManagerServiceTest { * Test that {@link BackupManagerService#dump()} dumps system user information before non-system * user information. */ - @Test public void testDump_systemUserFirst() { String[] args = new String[0]; @@ -539,16 +537,31 @@ public class BackupManagerServiceTest { } @Test - @Ignore("b/147012496") - public void testGetUserForAncestralSerialNumber() { + public void testGetUserForAncestralSerialNumber_forSystemUser() { BackupManagerServiceTestable.sBackupDisabled = false; BackupManagerService backupManagerService = new BackupManagerServiceTestable(mContextMock, mUserServices); + when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false)) + .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM}); when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L); UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L); - assertThat(user).isEqualTo(UserHandle.of(1)); + assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM)); + } + + @Test + public void testGetUserForAncestralSerialNumber_forNonSystemUser() { + BackupManagerServiceTestable.sBackupDisabled = false; + BackupManagerService backupManagerService = + new BackupManagerServiceTestable(mContextMock, mUserServices); + when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false)) + .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM}); + when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L); + + UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L); + + assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index ac555fda2204..3a8258be5f01 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -51,6 +51,7 @@ import androidx.annotation.NonNull; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; +import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import java.io.File; @@ -223,6 +224,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() { + return services.persistentDataBlockManagerInternal; + } + + @Override Looper getMyLooper() { return Looper.getMainLooper(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 175c7565a005..45729e5fd41f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -31,7 +31,10 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; import static com.android.server.testutils.TestUtils.assertExpectException; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; @@ -62,6 +65,7 @@ import android.app.Notification; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.PasswordMetrics; import android.app.timedetector.ManualTimeSuggestion; import android.app.timezonedetector.ManualTimeZoneSuggestion; @@ -272,6 +276,29 @@ public class DevicePolicyManagerTest extends DpmTestBase { }).when(getServices().userManager).getApplicationRestrictions( anyString(), any(UserHandle.class)); + // Emulate UserManager.setUserRestriction/getUserRestrictions + final Map<UserHandle, Bundle> userRestrictions = new HashMap<>(); + + doAnswer((Answer<Void>) invocation -> { + String key = (String) invocation.getArguments()[0]; + boolean value = (Boolean) invocation.getArguments()[1]; + UserHandle user = (UserHandle) invocation.getArguments()[2]; + Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle()); + userBundle.putBoolean(key, value); + + userRestrictions.put(user, userBundle); + return null; + }).when(getServices().userManager).setUserRestriction( + anyString(), anyBoolean(), any(UserHandle.class)); + + doAnswer((Answer<Boolean>) invocation -> { + String key = (String) invocation.getArguments()[0]; + UserHandle user = (UserHandle) invocation.getArguments()[1]; + Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle()); + return userBundle.getBoolean(key); + }).when(getServices().userManager).hasUserRestriction( + anyString(), any(UserHandle.class)); + // Add the first secondary user. getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, UserManager.USER_TYPE_FULL_SECONDARY); @@ -819,10 +846,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID); - // Setup device owner. mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.packageName = admin1.getPackageName(); - setupDeviceOwner(); // Add a managed profile belonging to the system user. addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); @@ -830,18 +855,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Change the parent user's password. dpm.reportPasswordChanged(UserHandle.USER_SYSTEM); - // Both the device owner and the managed profile owner should receive this broadcast. + // The managed profile owner should receive this broadcast. final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED); intent.setComponent(admin1); intent.putExtra(Intent.EXTRA_USER, UserHandle.of(UserHandle.USER_SYSTEM)); verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntent(intent), - MockUtils.checkUserHandle(UserHandle.USER_SYSTEM), - eq(null), - any(Bundle.class)); - verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( - MockUtils.checkIntent(intent), MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID), eq(null), any(Bundle.class)); @@ -861,12 +881,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID); - // Setup device owner. + // Configure system as having separate profile challenge. mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.packageName = admin1.getPackageName(); doReturn(true).when(getServices().lockPatternUtils) .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID); - setupDeviceOwner(); // Add a managed profile belonging to the system user. addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); @@ -951,6 +970,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().iactivityManager, times(1)).updateDeviceOwner( eq(admin1.getPackageName())); + verify(getServices().userManager, times(1)).setUserRestriction( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(true), eq(UserHandle.SYSTEM)); + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED), MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); @@ -2002,12 +2025,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertNoDeviceOwnerRestrictions(); - // Initialize DPMS again and check that the user restriction wasn't enabled again. reset(getServices().userManagerInternal); - initializeDpms(); - assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName())); - assertNotNull(dpms.getDeviceOwnerAdminLocked()); + // Ensure the DISALLOW_REMOVE_MANAGED_PROFILES restriction doesn't show up as a + // restriction to the device owner. + dpm.addUserRestriction(admin1, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); assertNoDeviceOwnerRestrictions(); } @@ -2022,6 +2044,116 @@ public class DevicePolicyManagerTest extends DpmTestBase { ); } + public void testSetFactoryResetProtectionPolicyWithDO() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + + when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn( + DpmMockContext.CALLER_UID); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(new ArrayList<>()) + .setFactoryResetProtectionDisabled(true) + .build(); + dpm.setFactoryResetProtectionPolicy(admin1, policy); + + FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1); + assertThat(result).isEqualTo(policy); + assertPoliciesAreEqual(policy, result); + + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + } + + public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception { + setupProfileOwner(); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionDisabled(true) + .build(); + + assertExpectException(SecurityException.class, null, + () -> dpm.setFactoryResetProtectionPolicy(admin1, policy)); + } + + public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice() + throws Exception { + setupProfileOwner(); + configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE); + + when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn( + DpmMockContext.CALLER_UID); + + List<String> accounts = new ArrayList<>(); + accounts.add("Account 1"); + accounts.add("Account 2"); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(accounts) + .build(); + + dpm.setFactoryResetProtectionPolicy(admin1, policy); + + FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1); + assertThat(result).isEqualTo(policy); + assertPoliciesAreEqual(policy, result); + + verify(mContext.spiedContext, times(2)).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + } + + public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent() + throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn( + DpmMockContext.CALLER_UID); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(new ArrayList<>()) + .setFactoryResetProtectionDisabled(true) + .build(); + + dpm.setFactoryResetProtectionPolicy(admin1, policy); + + mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + dpm.setActiveAdmin(admin1, /*replace=*/ false); + FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null); + assertThat(result).isEqualTo(policy); + assertPoliciesAreEqual(policy, result); + + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + } + + private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy, + FactoryResetProtectionPolicy actualPolicy) { + assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo( + expectedPolicy.isFactoryResetProtectionDisabled()); + assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(), + actualPolicy.getFactoryResetProtectionAccounts()); + } + + private void assertAccountsAreEqual(List<String> expectedAccounts, + List<String> actualAccounts) { + assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts); + } + public void testGetMacAddress() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); @@ -2845,6 +2977,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false); @@ -2856,6 +2989,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); assertCheckProvisioningPreCondition( @@ -2882,6 +3017,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false); @@ -2890,6 +3026,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Test again when split user is on when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, true); @@ -2901,6 +3038,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED); assertCheckProvisioningPreCondition( @@ -2913,6 +3052,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED); assertCheckProvisioningPreCondition( @@ -2938,6 +3079,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false /* because of non-split user */); @@ -2951,6 +3093,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( @@ -2981,7 +3125,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_nonSplitUser_withDo_primaryUser(); final int MANAGED_PROFILE_USER_ID = 18; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308); - addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false /* we can't remove a managed profile */)).thenReturn(false); when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, @@ -2995,6 +3138,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false/* because of completed device setup */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + false/* because of completed device setup */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false/* because of non-split user */); @@ -3008,6 +3153,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_USER_SETUP_COMPLETED); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_USER_SETUP_COMPLETED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( @@ -3024,43 +3171,21 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_HAS_DEVICE_OWNER); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_HAS_DEVICE_OWNER); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false); - // COMP mode is allowed. + // COMP mode NOT is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); - // And other DPCs can also provision a managed profile (DO + BYOD case). + // And other DPCs can NOT provision a managed profile. assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true, - DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); - } - - public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception { - setup_nonSplitUser_withDo_primaryUser(); - mContext.packageName = admin1.getPackageName(); - mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); - // The DO should be allowed to initiate provisioning if it set the restriction itself, but - // other packages should be forbidden. - when(getServices().userManager.hasUserRestriction( - eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) - .thenReturn(true); - when(getServices().userManager.getUserRestrictionSource( - eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) - .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); - assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); - assertCheckProvisioningPreCondition( - DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } @@ -3081,31 +3206,46 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } - public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception { + public void testCheckCannotSetProfileOwnerWithDeviceOwner() throws Exception { + setup_nonSplitUser_withDo_primaryUser(); + final int managedProfileUserId = 18; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 1308); + + final int userId = UserHandle.getUserId(managedProfileAdminUid); + getServices().addUser(userId, 0, UserManager.USER_TYPE_PROFILE_MANAGED, + UserHandle.USER_SYSTEM); + mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS); + setUpPackageManagerForFakeAdmin(admin1, managedProfileAdminUid, admin1); + dpm.setActiveAdmin(admin1, false, userId); + assertFalse(dpm.setProfileOwner(admin1, null, userId)); + mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS); + } + + public void testCheckProvisioningPreCondition_nonSplitUser_attemptingComp() throws Exception { setup_nonSplitUser_withDo_primaryUser_ManagedProfile(); mContext.packageName = admin1.getPackageName(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); // We can delete the managed profile to create a new one, so provisioning is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true, + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } @@ -3133,8 +3273,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { // But the device owner can still do it because it has set the restriction itself. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } private void setup_splitUser_firstBoot_systemUser() throws Exception { @@ -3153,6 +3293,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* because canAddMoreManagedProfiles returns false */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -3167,6 +3308,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER); assertCheckProvisioningPreCondition( @@ -3193,6 +3336,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* because canAddMoreManagedProfiles returns false */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -3207,6 +3352,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER); assertCheckProvisioningPreCondition( @@ -3232,6 +3379,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, true); @@ -3244,6 +3392,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( @@ -3271,6 +3421,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); @@ -3284,6 +3436,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( @@ -3329,6 +3483,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, true)).thenReturn(true); setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE); @@ -3341,7 +3497,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); mContext.packageName = admin1.getPackageName(); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } public void testCheckProvisioningPreCondition_provisionManagedProfileWithDeviceOwner_primaryUser() @@ -3349,9 +3505,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); - // COMP mode is allowed. + // COMP mode is NOT allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); } private void setup_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception { @@ -3868,11 +4024,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); MoreAsserts.assertEmpty(targetUsers); - // Setup a managed profile managed by the same admin. - final int MANAGED_PROFILE_USER_ID = 15; - final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456); - addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); - // Add a secondary user, it should never talk with. final int ANOTHER_USER_ID = 36; getServices().addUser(ANOTHER_USER_ID, 0, UserManager.USER_TYPE_FULL_SECONDARY); @@ -3882,30 +4033,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); MoreAsserts.assertEmpty(targetUsers); - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertEmpty(targetUsers); - // Setting affiliation ids final Set<String> userAffiliationIds = Collections.singleton("some.affiliation-id"); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; dpm.setAffiliationIds(admin1, userAffiliationIds); - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - dpm.setAffiliationIds(admin1, userAffiliationIds); - - // Calling from device owner admin, the result list should just contain the managed - // profile user id. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.of(MANAGED_PROFILE_USER_ID)); - - // Calling from managed profile admin, the result list should just contain the system - // user id. - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.SYSTEM); - // Changing affiliation ids in one dpm.setAffiliationIds(admin1, Collections.singleton("some-different-affiliation-id")); @@ -3919,38 +4051,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { MoreAsserts.assertEmpty(targetUsers); } - public void testGetBindDeviceAdminTargetUsers_differentPackage() throws Exception { - // Setup a device owner. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - setupDeviceOwner(); - - // Set up a managed profile managed by different package. - final int MANAGED_PROFILE_USER_ID = 15; - final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456); - final ComponentName adminDifferentPackage = - new ComponentName("another.package", "whatever.class"); - addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2); - - // Setting affiliation ids - final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id"); - dpm.setAffiliationIds(admin1, userAffiliationIds); - - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds); - - // Calling from device owner admin, we should get zero bind device admin target users as - // their packages are different. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertEmpty(targetUsers); - - // Calling from managed profile admin, we should still get zero target users for the same - // reason. - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(adminDifferentPackage); - MoreAsserts.assertEmpty(targetUsers); - } - private void verifyLockTaskState(int userId) throws Exception { verifyLockTaskState(userId, new String[0], DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS); @@ -3987,79 +4087,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { () -> dpm.setLockTaskFeatures(who, flags)); } - public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception { - // Setup a device owner. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - setupDeviceOwner(); - // Lock task policy is updated when loading user data. - verifyLockTaskState(UserHandle.USER_SYSTEM); - - // Set up a managed profile managed by different package (package name shouldn't matter) - final int MANAGED_PROFILE_USER_ID = 15; - final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456); - final ComponentName adminDifferentPackage = - new ComponentName("another.package", "whatever.class"); - addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2); - verifyLockTaskState(MANAGED_PROFILE_USER_ID); - - // Setup a PO on the secondary user - mContext.binder.callingUid = DpmMockContext.CALLER_UID; - setAsProfileOwner(admin3); - verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE); - - // The DO can still set lock task packages - final String[] doPackages = {"doPackage1", "doPackage2"}; - final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS - | DevicePolicyManager.LOCK_TASK_FEATURE_HOME - | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; - verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags); - - final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"}; - final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS - | DevicePolicyManager.LOCK_TASK_FEATURE_HOME - | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; - verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags); - - // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages. - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - final String[] poPackages = {"poPackage1", "poPackage2"}; - final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS - | DevicePolicyManager.LOCK_TASK_FEATURE_HOME - | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; - verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags); - - // Setting same affiliation ids - final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id"); - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - dpm.setAffiliationIds(admin1, userAffiliationIds); - - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds); - - // Now the managed profile can set lock task packages. - dpm.setLockTaskPackages(adminDifferentPackage, poPackages); - MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage)); - assertTrue(dpm.isLockTaskPermitted("poPackage1")); - assertFalse(dpm.isLockTaskPermitted("doPackage2")); - // And it can set lock task features. - dpm.setLockTaskFeatures(adminDifferentPackage, poFlags); - verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags); - - // Unaffiliate the profile, lock task mode no longer available on the profile. - dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet()); - assertFalse(dpm.isLockTaskPermitted("poPackage1")); - // Lock task packages cleared when loading user data and when the user becomes unaffiliated. - verify(getServices().iactivityManager, times(2)).updateLockTaskPackages( - MANAGED_PROFILE_USER_ID, new String[0]); - verify(getServices().iactivityTaskManager, times(2)).updateLockTaskFeatures( - MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE); - - // Verify that lock task packages were not cleared for the DO - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - assertTrue(dpm.isLockTaskPermitted("doPackage1")); - - } - public void testLockTaskPolicyForProfileOwner() throws Exception { // Setup a PO mContext.binder.callingUid = DpmMockContext.CALLER_UID; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java new file mode 100644 index 000000000000..bc853c693b3a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.devicepolicy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.admin.FactoryResetProtectionPolicy; +import android.os.Parcel; +import android.util.Xml; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.FastXmlSerializer; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}. + * + * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest + */ +@RunWith(AndroidJUnit4.class) +public class FactoryResetProtectionPolicyTest { + + private static final String TAG_FACTORY_RESET_PROTECTION_POLICY = + "factory_reset_protection_policy"; + + @Test + public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception { + List<String> accounts = new ArrayList<>(); + accounts.add("Account 1"); + accounts.add("Account 2"); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(accounts) + .setFactoryResetProtectionDisabled(true) + .build(); + + testParcelAndUnparcel(policy); + testSerializationAndDeserialization(policy); + } + + @Test + public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception { + List<String> accounts = new ArrayList<>(); + accounts.add("Account 1"); + accounts.add("Account 2"); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(accounts) + .setFactoryResetProtectionDisabled(true) + .build(); + + testParcelAndUnparcel(policy); + testInvalidXmlSerializationAndDeserialization(policy); + } + + private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) { + Parcel parcel = Parcel.obtain(); + policy.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + FactoryResetProtectionPolicy actualPolicy = + FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel); + assertPoliciesAreEqual(policy, actualPolicy); + parcel.recycle(); + } + + private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy) + throws Exception { + ByteArrayOutputStream outStream = serialize(policy); + ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray()); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new InputStreamReader(inStream)); + assertEquals(XmlPullParser.START_TAG, parser.next()); + + assertPoliciesAreEqual(policy, policy.readFromXml(parser)); + } + + private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy) + throws Exception { + ByteArrayOutputStream outStream = serialize(policy); + ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray()); + XmlPullParser parser = mock(XmlPullParser.class); + when(parser.next()).thenThrow(XmlPullParserException.class); + parser.setInput(new InputStreamReader(inStream)); + + // If deserialization fails, then null is returned. + assertNull(policy.readFromXml(parser)); + } + + private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy) + throws IOException { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + final XmlSerializer outXml = new FastXmlSerializer(); + outXml.setOutput(outStream, StandardCharsets.UTF_8.name()); + outXml.startDocument(null, true); + outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + policy.writeToXml(outXml); + outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + outXml.endDocument(); + outXml.flush(); + return outStream; + } + + private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy, + FactoryResetProtectionPolicy actualPolicy) { + assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(), + actualPolicy.isFactoryResetProtectionDisabled()); + assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(), + actualPolicy.getFactoryResetProtectionAccounts()); + } + + private void assertAccountsAreEqual(List<String> expectedAccounts, + List<String> actualAccounts) { + assertEquals(expectedAccounts.size(), actualAccounts.size()); + for (int i = 0; i < expectedAccounts.size(); i++) { + assertEquals(expectedAccounts.get(i), actualAccounts.get(i)); + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 919a3f6d7d1b..6c2c1446a459 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -68,6 +68,7 @@ import android.view.IWindowManager; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; +import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -117,6 +118,7 @@ public class MockSystemServices { public final TimeDetector timeDetector; public final TimeZoneDetector timeZoneDetector; public final KeyChain.KeyChainConnection keyChainConnection; + public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; public final BuildMock buildMock = new BuildMock(); @@ -160,6 +162,7 @@ public class MockSystemServices { timeDetector = mock(TimeDetector.class); timeZoneDetector = mock(TimeZoneDetector.class); keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS); + persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(realContext.getPackageManager()); diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java index 63189e7546e2..5aed194773f5 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java @@ -16,14 +16,7 @@ package com.android.server.integrity; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItems; -import static org.junit.Assert.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import android.content.integrity.AppInstallMetadata; import android.content.integrity.AtomicFormula; @@ -33,26 +26,26 @@ import android.content.integrity.CompoundFormula; import android.content.integrity.Rule; import android.util.Slog; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.integrity.parser.RuleXmlParser; -import com.android.server.integrity.serializer.RuleXmlSerializer; +import com.android.server.integrity.parser.RuleBinaryParser; +import com.android.server.integrity.serializer.RuleBinarySerializer; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; /** Unit test for {@link IntegrityFileManager} */ -@RunWith(AndroidJUnit4.class) +@RunWith(JUnit4.class) public class IntegrityFileManagerTest { private static final String TAG = "IntegrityFileManagerTest"; @@ -72,7 +65,7 @@ public class IntegrityFileManagerTest { // Use Xml Parser/Serializer to help with debugging since we can just print the file. mIntegrityFileManager = new IntegrityFileManager( - new RuleXmlParser(), new RuleXmlSerializer(), mTmpDir); + new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir); Files.walk(mTmpDir.toPath()) .forEach( path -> { @@ -97,12 +90,19 @@ public class IntegrityFileManagerTest { @Test public void testGetMetadata() throws Exception { - assertNull(mIntegrityFileManager.readMetadata()); + assertThat(mIntegrityFileManager.readMetadata()).isNull(); mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); - assertNotNull(mIntegrityFileManager.readMetadata()); - assertEquals(VERSION, mIntegrityFileManager.readMetadata().getVersion()); - assertEquals(RULE_PROVIDER, mIntegrityFileManager.readMetadata().getRuleProvider()); + assertThat(mIntegrityFileManager.readMetadata()).isNotNull(); + assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION); + assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER); + } + + @Test + public void testIsInitialized() throws Exception { + assertThat(mIntegrityFileManager.initialized()).isFalse(); + mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); + assertThat(mIntegrityFileManager.initialized()).isTrue(); } @Test @@ -110,20 +110,8 @@ public class IntegrityFileManagerTest { String packageName = "package"; String packageCert = "cert"; int version = 123; - Rule packageNameRule = - new Rule( - new StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - Rule.DENY); - Rule packageCertRule = - new Rule( - new StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - packageCert, - /* isHashedValue= */ false), - Rule.DENY); + Rule packageNameRule = getPackageNameIndexedRule(packageName); + Rule packageCertRule = getAppCertificateIndexedRule(packageCert); Rule versionCodeRule = new Rule( new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, version), @@ -142,9 +130,7 @@ public class IntegrityFileManagerTest { AtomicFormula.LE, version))), Rule.DENY); - // We will test the specifics of indexing in other classes. Here, we just require that all - // rules that are related to the given AppInstallMetadata are returned and do not assert - // anything on other rules. + List<Rule> rules = Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule); mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules); @@ -159,17 +145,77 @@ public class IntegrityFileManagerTest { .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); - assertThat(rulesFetched, hasItems( - equalTo(packageNameRule), - equalTo(packageCertRule), - equalTo(versionCodeRule) - )); + assertThat(rulesFetched) + .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule); } @Test - public void testIsInitialized() throws Exception { - assertFalse(mIntegrityFileManager.initialized()); - mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); - assertTrue(mIntegrityFileManager.initialized()); + public void testGetRules_indexedForManyRules() throws Exception { + String packageName = "package"; + String installerName = "installer"; + String appCertificate = "cert"; + + // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and + // 500 unindexed rules. + List<Rule> rules = new ArrayList<>(); + + for (int i = 0; i < 2500; i++) { + rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i))); + rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i))); + } + + for (int i = 0; i < 70; i++) { + rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i))); + } + + // Write the rules and get them indexed. + mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules); + + // Read the rules for a specific rule. + String installedPackageName = String.format("%s%04d", packageName, 264); + String installedAppCertificate = String.format("%s%04d", appCertificate, 1264); + AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() + .setPackageName(installedPackageName) + .setAppCertificate(installedAppCertificate) + .setVersionCode(250) + .setInstallerName("abc") + .setInstallerCertificate("abc") + .setIsPreInstalled(true) + .build(); + List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); + + // Verify that we do not load all the rules and we have the necessary rules to evaluate. + assertThat(rulesFetched.size()).isEqualTo(270); + assertThat(rulesFetched) + .containsAllOf( + getPackageNameIndexedRule(installedPackageName), + getAppCertificateIndexedRule(installedAppCertificate)); + } + + private Rule getPackageNameIndexedRule(String packageName) { + return new Rule( + new StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName, + /* isHashedValue= */ false), + Rule.DENY); + } + + private Rule getAppCertificateIndexedRule(String appCertificate) { + return new Rule( + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, + appCertificate, + /* isHashedValue= */ false), + Rule.DENY); + } + + private Rule getInstallerCertificateRule(String installerCert) { + return new Rule( + new StringAtomicFormula( + AtomicFormula.INSTALLER_NAME, + installerCert, + /* isHashedValue= */ false), + Rule.DENY); } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java index 1eb5eb51504a..4fa73028ece1 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java @@ -121,6 +121,26 @@ public class BitTrackedInputStreamTest { } @Test + public void testBitTrackedInputStream_canReadMoreRules() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + assertThat(bitTrackedInputStream.canReadMoreRules(2)).isTrue(); + + // Read until the string parameter. + String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream); + + // Verify that the read bytes are counted. + assertThat(stringValue).isEqualTo(packageName); + assertThat(bitTrackedInputStream.canReadMoreRules(2)).isFalse(); + } + + @Test public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException { String packageName = "com.test.app"; byte[] testInput = diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 51f5c755754c..881b3d6bba52 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -48,7 +48,6 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -656,65 +655,4 @@ public class RuleBinaryParserTest { /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit", () -> binaryParser.parse(rule.array())); } - - @Test - public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly() - throws Exception { - String packageName2 = "com.test.2"; - - byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1")); - byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2)); - byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3")); - - ByteBuffer rule = - ByteBuffer.allocate( - DEFAULT_FORMAT_VERSION_BYTES.length - + ruleBytes1.length - + ruleBytes2.length - + ruleBytes3.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes1); - rule.put(ruleBytes2); - rule.put(ruleBytes3); - InputStream inputStream = new ByteArrayInputStream(rule.array()); - - RuleParser binaryParser = new RuleBinaryParser(); - - List<RuleIndexRange> indexRanges = new ArrayList<>(); - indexRanges.add( - new RuleIndexRange( - DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length, - DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length - + ruleBytes2.length)); - List<Rule> rules = binaryParser.parse(inputStream, indexRanges); - - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName2, - /* isHashedValue= */ false))), - Rule.DENY); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - private static String getRulesWithPackageName(String packageName) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - - } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java index 742952e056bc..291cfeea5bbc 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java @@ -115,7 +115,8 @@ public class RuleIndexingControllerTest { + getKeyValueString(START_INDEXING_KEY, 500) + getKeyValueString(END_INDEXING_KEY, 900) + getKeyValueString(START_INDEXING_KEY, 900) - + getKeyValueString(END_INDEXING_KEY, 945)); + + getKeyValueString(END_INDEXING_KEY, 945) + + getBits(1, 1)); ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); rule.put(stringBytes); InputStream inputStream = new ByteArrayInputStream(rule.array()); @@ -152,7 +153,8 @@ public class RuleIndexingControllerTest { + getKeyValueString("888", 800) + getKeyValueString(END_INDEXING_KEY, 900) + getKeyValueString(START_INDEXING_KEY, 900) - + getKeyValueString(END_INDEXING_KEY, 945)); + + getKeyValueString(END_INDEXING_KEY, 945) + + getBits(1, 1)); ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); rule.put(stringBytes); return new ByteArrayInputStream(rule.array()); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java index eb6698b0d479..bc2aac0acf10 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -135,15 +135,14 @@ public class RuleBinarySerializerTest { .isEqualTo(expectedRuleOutputStream.toByteArray()); ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); + String serializedIndexingBytes = + SERIALIZED_START_INDEXING_KEY + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + + SERIALIZED_END_INDEXING_KEY + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */32); byte[] expectedIndexingBytes = - getBytes( - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ - 32)); - expectedIndexingOutputStream.write(expectedIndexingBytes); - expectedIndexingOutputStream.write(expectedIndexingBytes); + getBytes(serializedIndexingBytes + serializedIndexingBytes + + serializedIndexingBytes + getBits(1, 1)); expectedIndexingOutputStream.write(expectedIndexingBytes); assertThat(indexingOutputStream.toByteArray()) .isEqualTo(expectedIndexingOutputStream.toByteArray()); @@ -197,15 +196,16 @@ public class RuleBinarySerializerTest { + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + SERIALIZED_END_INDEXING_KEY + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed)); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed)); String expectedIndexingBitsForUnindexed = SERIALIZED_START_INDEXING_KEY + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length + getBytes( - expectedBits).length, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForUnindexed)); + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length + + getBytes(expectedBits).length, /* numOfBits= */ 32); + expectedIndexingOutputStream.write(getBytes( + expectedIndexingBitsForIndexed + expectedIndexingBitsForIndexed + + expectedIndexingBitsForUnindexed + getBits(1, 1))); + assertThat(indexingOutputStream.toByteArray()) .isEqualTo(expectedIndexingOutputStream.toByteArray()); } @@ -564,7 +564,6 @@ public class RuleBinarySerializerTest { expectedIndexingBytesForPackageNameIndexed += SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForPackageNameIndexed)); String expectedIndexingBytesForAppCertificateIndexed = SERIALIZED_START_INDEXING_KEY @@ -588,7 +587,6 @@ public class RuleBinarySerializerTest { expectedIndexingBytesForAppCertificateIndexed += SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForAppCertificateIndexed)); String expectedIndexingBytesForUnindexed = SERIALIZED_START_INDEXING_KEY @@ -603,8 +601,11 @@ public class RuleBinarySerializerTest { expectedIndexingBytesForUnindexed += SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForUnindexed)); - + expectedIndexingOutputStream.write( + getBytes(expectedIndexingBytesForPackageNameIndexed + + expectedIndexingBytesForAppCertificateIndexed + + expectedIndexingBytesForUnindexed + + getBits(1, 1))); assertThat(ruleOutputStream.toByteArray()) .isEqualTo(expectedOrderedRuleOutputStream.toByteArray()); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java index 55fada44b99e..1674422f3af9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java @@ -38,10 +38,8 @@ import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; /** Unit tests for {@link RuleIndexingDetailsIdentifier}. */ @RunWith(JUnit4.class) @@ -140,7 +138,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_PACKAGE_NAME); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); // Verify the resulting map content. assertThat(result.keySet()) @@ -157,7 +155,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_APP_CERTIFICATE); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -174,7 +172,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -189,7 +187,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -215,7 +213,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(negatedRule); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -225,7 +223,7 @@ public class RuleIndexingDetailsIdentifierTest { } @Test - public void getIndexType_allRulesTogetherInValidOrder() { + public void getIndexType_allRulesTogetherSplitCorrectly() { Rule packageNameRuleA = getRuleWithPackageName("aaa"); Rule packageNameRuleB = getRuleWithPackageName("bbb"); Rule packageNameRuleC = getRuleWithPackageName("ccc"); @@ -243,38 +241,20 @@ public class RuleIndexingDetailsIdentifierTest { ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); // We check asserts this way to ensure ordering based on package name. assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc"); - Iterator<String> keySetIterator = result.get(PACKAGE_NAME_INDEXED).keySet().iterator(); - assertThat(keySetIterator.next()).isEqualTo("aaa"); - assertThat(keySetIterator.next()).isEqualTo("bbb"); - assertThat(keySetIterator.next()).isEqualTo("ccc"); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("aaa")).containsExactly(packageNameRuleA); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("bbb")).containsExactly(packageNameRuleB); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("ccc")).containsExactly(packageNameRuleC); // We check asserts this way to ensure ordering based on app certificate. assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2", "cert3"); - keySetIterator = result.get(APP_CERTIFICATE_INDEXED).keySet().iterator(); - assertThat(keySetIterator.next()).isEqualTo("cert1"); - assertThat(keySetIterator.next()).isEqualTo("cert2"); - assertThat(keySetIterator.next()).isEqualTo("cert3"); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert1")).containsExactly( - certificateRule1); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert2")).containsExactly( - certificateRule2); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert3")).containsExactly( - certificateRule3); assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly( - RULE_WITH_INSTALLER_RESTRICTIONS, + .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS, RULE_WITH_NONSTRING_RESTRICTIONS); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index 821d97acc230..670bd8107bed 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -67,13 +67,25 @@ import javax.crypto.KeyGenerator; public class PlatformKeyManagerTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; - private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; + private static final int MIN_GENERATION_ID = 1000000; + private static final int PRIMARY_USER_ID_FIXTURE = 0; private static final int USER_ID_FIXTURE = 42; private static final long USER_SID = 4200L; private static final String KEY_ALGORITHM = "AES"; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias"; + private static final String ENCRYPTION_KEY_ALIAS_1 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt"; + private static final String DECRYPTION_KEY_ALIAS_1 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt"; + private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 = + "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt"; + private static final String ENCRYPTION_KEY_ALIAS_2 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt"; + private static final String DECRYPTION_KEY_ALIAS_2 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt"; + @Mock private Context mContext; @Mock private KeyStoreProxy mKeyStoreProxy; @Mock private KeyguardManager mKeyguardManager; @@ -114,7 +126,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any(), any()); } @@ -156,7 +168,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -187,19 +199,33 @@ public class PlatformKeyManagerTest { } @Test - public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception { + public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception { + mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE); + + assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired()); + } + + @Test + public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception { + mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE); + + assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired()); + } + + @Test + public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired() + throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired()); + assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired()); } @Test - public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception { + public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired() + throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals( - USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS, - getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds()); + assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired()); } @Test @@ -219,7 +245,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy, never()).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -231,7 +257,7 @@ public class PlatformKeyManagerTest { expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE)); verify(mKeyStoreProxy, never()).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -251,15 +277,15 @@ public class PlatformKeyManagerTest { public void init_savesGenerationIdToDatabase() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, + assertEquals(MIN_GENERATION_ID, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE)); } @Test - public void init_setsGenerationIdTo1() throws Exception { + public void init_setsGenerationId() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -268,22 +294,20 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -294,226 +318,194 @@ public class PlatformKeyManagerTest { @Test public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any())).thenReturn(generateAndroidKeyStoreKey()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); } @@ -523,7 +515,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); - assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -533,17 +525,17 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); mPlatformKeyManager.regenerate(USER_ID_FIXTURE); // Removes second generation keys. verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_2)); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt")); + eq(DECRYPTION_KEY_ALIAS_2)); } @Test @@ -553,7 +545,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any(), any()); } @@ -565,14 +557,14 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any(), any()); } private KeyProtection getEncryptKeyProtection() throws Exception { verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any(), mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); @@ -580,7 +572,15 @@ public class PlatformKeyManagerTest { private KeyProtection getDecryptKeyProtection() throws Exception { verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), + any(), + mProtectionParameterCaptor.capture()); + return (KeyProtection) mProtectionParameterCaptor.getValue(); + } + + private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception { + verify(mKeyStoreProxy).setEntry( + eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1), any(), mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 68900175cc8f..ac7447006444 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; @@ -84,7 +85,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Map; import java.util.Random; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -157,7 +158,7 @@ public class RecoverableKeyStoreManagerTest { @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private ApplicationKeyStorage mApplicationKeyStorage; @Mock private CleanupManager mCleanupManager; - @Mock private ExecutorService mExecutorService; + @Mock private ScheduledExecutorService mExecutorService; @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; @@ -1253,7 +1254,7 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager.lockScreenSecretAvailable( LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11); - verify(mExecutorService).execute(any()); + verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any()); } @Test @@ -1263,7 +1264,7 @@ public class RecoverableKeyStoreManagerTest { "password".getBytes(), 11); - verify(mExecutorService).execute(any()); + verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any()); } private static byte[] encryptedApplicationKey( 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 c7dbad83e384..e1c489e65984 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -37,6 +37,7 @@ import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.TAG_ALL; +import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.TrafficStats.MB_IN_BYTES; @@ -1758,6 +1759,57 @@ public class NetworkPolicyManagerServiceTest { } /** + * Test that when StatsProvider triggers limit reached, new limit will be calculated and + * re-armed. + */ + @Test + public void testStatsProviderLimitReached() throws Exception { + final int CYCLE_DAY = 15; + + final NetworkStats stats = new NetworkStats(0L, 1); + stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, + 2999, 1, 2000, 1, 0); + when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) + .thenReturn(stats.getTotalBytes()); + when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) + .thenReturn(stats); + + // Get active mobile network in place + expectMobileDefaults(); + mService.updateNetworks(); + verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE); + + // Set limit to 10KB. + setNetworkPolicies(new NetworkPolicy( + sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L, + true)); + postMsgAndWaitForCompletion(); + + // Verifies that remaining quota is set to providers. + verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L); + + reset(mStatsService); + + // Increase the usage. + stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, + 1000, 1, 999, 1, 0); + when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) + .thenReturn(stats.getTotalBytes()); + when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) + .thenReturn(stats); + + // Simulates that limit reached fires earlier by provider, but actually the quota is not + // yet reached. + final NetworkPolicyManagerInternal npmi = LocalServices + .getService(NetworkPolicyManagerInternal.class); + npmi.onStatsProviderLimitReached("TEST"); + + // Verifies that the limit reached leads to a force update. + postMsgAndWaitForCompletion(); + verify(mStatsService).forceUpdate(); + } + + /** * Exhaustively test isUidNetworkingBlocked to output the expected results based on external * conditions. */ diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java index a83d94001cf8..f871203728c0 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java @@ -98,7 +98,7 @@ public class AppDataRollbackHelperTest { final int[] installedUsers) { return new PackageRollbackInfo( new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1), - new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers), + new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers), new SparseLongArray()); } diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java index 757a884f8ded..d0d2edc59861 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -87,13 +87,15 @@ public class RollbackStoreTest { + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55}," + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':" + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'}," - + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':" + + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false," + + "'installedUsers':" + "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6}," + "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546," + "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips'," + "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test'," + "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18," - + "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79]," + + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false," + + "'installedUsers':[55,79]," + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello'," + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}]," + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z'," @@ -155,7 +157,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("com.made.up", 18), new VersionedPackage("com.something.else", 5), new IntArray(), - new ArrayList<>(), false, new IntArray(), new SparseLongArray()); + new ArrayList<>(), false, false, new IntArray(), new SparseLongArray()); pkgInfo1.getPendingBackups().add(8); pkgInfo1.getPendingBackups().add(888); pkgInfo1.getPendingBackups().add(88885); @@ -175,7 +177,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo( new VersionedPackage("another.package", 2), new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo2.getPendingBackups().add(57); pkgInfo2.getPendingRestores().add( @@ -205,7 +207,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55), new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo1.getPendingBackups().add(59); pkgInfo1.getPendingBackups().add(1245); pkgInfo1.getPendingBackups().add(124544); @@ -224,7 +226,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28), new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo2.getPendingBackups().add(5); pkgInfo2.getPendingRestores().add( diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index e368d634b968..164c88382828 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -295,7 +295,8 @@ public class RollbackUnitTest { String packageName, long fromVersion, long toVersion, boolean isApex) { return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion), new VersionedPackage(packageName, toVersion), - new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray()); + new IntArray(), new ArrayList<>(), isApex, false, new IntArray(), + new SparseLongArray()); } private static class PackageRollbackInfoForPackage implements diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index f8915c06b555..cc170af2c57b 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -37,6 +37,7 @@ import android.hardware.audio.common.V2_0.Uuid; import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange; import android.media.audio.common.AudioChannelMask; import android.media.audio.common.AudioFormat; +import android.media.soundtrigger_middleware.AudioCapabilities; import android.media.soundtrigger_middleware.ConfidenceLevel; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -59,6 +60,7 @@ import android.os.HwParcel; import android.os.IHwBinder; import android.os.IHwInterface; import android.os.RemoteException; +import android.util.Pair; import org.junit.Before; import org.junit.Test; @@ -155,7 +157,19 @@ public class SoundTriggerMiddlewareImplTest { return properties; } - private static void validateDefaultProperties(SoundTriggerModuleProperties properties, + private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3( + boolean supportConcurrentCapture) { + android.hardware.soundtrigger.V2_3.Properties properties = + new android.hardware.soundtrigger.V2_3.Properties(); + properties.base = createDefaultProperties(supportConcurrentCapture); + properties.supportedModelArch = "supportedModelArch"; + properties.audioCapabilities = + android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION + | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION; + return properties; + } + + private void validateDefaultProperties(SoundTriggerModuleProperties properties, boolean supportConcurrentCapture) { assertEquals("implementor", properties.implementor); assertEquals("description", properties.description); @@ -173,8 +187,23 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(supportConcurrentCapture, properties.concurrentCapture); assertTrue(properties.triggerInEvent); assertEquals(432, properties.powerConsumptionMw); + + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + assertEquals("supportedModelArch", properties.supportedModelArch); + assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION, + properties.audioCapabilities); + } else { + assertEquals("", properties.supportedModelArch); + assertEquals(0, properties.audioCapabilities); + } } + private void verifyNotGetProperties() throws RemoteException { + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver, + never()).getProperties(any()); + } + } private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0( int hwHandle, @@ -290,15 +319,36 @@ public class SoundTriggerMiddlewareImplTest { properties); return null; }).when(mHalDriver).getProperties(any()); + + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver = + (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver; + doAnswer(invocation -> { + android.hardware.soundtrigger.V2_3.Properties properties = + createDefaultProperties_2_3( + supportConcurrentCapture); + ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback) + invocation.getArgument( + 0)).onValues(0, + properties); + return null; + }).when(driver).getProperties_2_3(any()); + } + mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider); } - private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module, + int hwHandle) throws RemoteException { SoundModel model = createGenericSoundModel(); ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback = invocation.getArgument(1); @@ -317,7 +367,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.model = hwHandle; callback.soundModelCallback(modelEvent, callbackCookie); return null; - }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any()); + }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -334,17 +385,23 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid)); assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray()); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module, + int hwHandle) throws RemoteException { android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver = (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver; SoundModel model = createGenericSoundModel(); ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback = invocation.getArgument(1); @@ -363,7 +420,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.header.model = hwHandle; callback.soundModelCallback_2_1(modelEvent, callbackCookie); return null; - }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any()); + }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -381,10 +439,12 @@ public class SoundTriggerMiddlewareImplTest { assertArrayEquals(new byte[]{91, 92, 93, 94, 95}, HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data)); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module, + int hwHandle) throws RemoteException { if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { return loadGenericModel_2_1(module, hwHandle); } else { @@ -392,12 +452,17 @@ public class SoundTriggerMiddlewareImplTest { } } - private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module, + int hwHandle) throws RemoteException { PhraseSoundModel model = createPhraseSoundModel(); ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback = invocation.getArgument( @@ -419,7 +484,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.model = hwHandle; callback.soundModelCallback(modelEvent, callbackCookie); return null; - }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any()); + }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -451,11 +517,12 @@ public class SoundTriggerMiddlewareImplTest { | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION, hidlPhrase.recognitionModes); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module, + int hwHandle) throws RemoteException { android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver = (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver; @@ -463,6 +530,11 @@ public class SoundTriggerMiddlewareImplTest { ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback = invocation.getArgument( @@ -484,7 +556,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.header.model = hwHandle; callback.soundModelCallback_2_1(modelEvent, callbackCookie); return null; - }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any()); + }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -517,10 +590,12 @@ public class SoundTriggerMiddlewareImplTest { | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION, hidlPhrase.recognitionModes); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel( + ISoundTriggerModule module, int hwHandle) throws RemoteException { if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { return loadPhraseModel_2_1(module, hwHandle); } else { @@ -535,18 +610,14 @@ public class SoundTriggerMiddlewareImplTest { verify(mAudioSessionProvider).releaseSession(101); } - private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle, + private void startRecognition_2_0(ISoundTriggerModule module, int handle, int hwHandle) throws RemoteException { ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig> configCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class); - ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor = - ArgumentCaptor.forClass( - android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class); - ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); - when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), - callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0); + when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt())) + .thenReturn(0); RecognitionConfig config = createRecognitionConfig(); @@ -569,11 +640,9 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(234, halLevel.userId); assertEquals(34, halLevel.levelPercent); assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray()); - - return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()); } - private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle, + private void startRecognition_2_1(ISoundTriggerModule module, int handle, int hwHandle) throws RemoteException { android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver = (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver; @@ -581,13 +650,9 @@ public class SoundTriggerMiddlewareImplTest { ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig> configCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class); - ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor = - ArgumentCaptor.forClass( - android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class); - ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); - when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), - callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0); + when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt())) + .thenReturn(0); RecognitionConfig config = createRecognitionConfig(); @@ -611,16 +676,56 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(34, halLevel.levelPercent); assertArrayEquals(new byte[]{5, 4, 3, 2, 1}, HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data)); + } + + private void startRecognition_2_3(ISoundTriggerModule module, int handle, + int hwHandle) throws RemoteException { + android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver = + (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver; + + ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig> + configCaptor = ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_3.RecognitionConfig.class); - return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()); + when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0); + + RecognitionConfig config = createRecognitionConfig(); + + module.startRecognition(handle, config); + verify(driver).startRecognition_2_3(eq(hwHandle), any()); + + android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended = + configCaptor.getValue(); + android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 = + halConfigExtended.base; + + assertTrue(halConfig_2_1.header.captureRequested); + assertEquals(102, halConfig_2_1.header.captureHandle); + assertEquals(103, halConfig_2_1.header.captureDevice); + assertEquals(1, halConfig_2_1.header.phrases.size()); + android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra = + halConfig_2_1.header.phrases.get(0); + assertEquals(123, halPhraseExtra.id); + assertEquals(4, halPhraseExtra.confidenceLevel); + assertEquals(5, halPhraseExtra.recognitionModes); + assertEquals(1, halPhraseExtra.levels.size()); + android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0); + assertEquals(234, halLevel.userId); + assertEquals(34, halLevel.levelPercent); + assertArrayEquals(new byte[]{5, 4, 3, 2, 1}, + HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data)); + assertEquals(AudioCapabilities.ECHO_CANCELLATION + | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities); } - private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle, + private void startRecognition(ISoundTriggerModule module, int handle, int hwHandle) throws RemoteException { - if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { - return startRecognition_2_1(module, handle, hwHandle); + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + startRecognition_2_3(module, handle, hwHandle); + } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { + startRecognition_2_1(module, handle, hwHandle); } else { - return startRecognition_2_0(module, handle, hwHandle); + startRecognition_2_0(module, handle, hwHandle); } } @@ -635,6 +740,8 @@ public class SoundTriggerMiddlewareImplTest { config.phraseRecognitionExtras[0].levels[0].userId = 234; config.phraseRecognitionExtras[0].levels[0].levelPercent = 34; config.data = new byte[]{5, 4, 3, 2, 1}; + config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION + | AudioCapabilities.NOISE_SUPPRESSION; return config; } @@ -650,6 +757,9 @@ public class SoundTriggerMiddlewareImplTest { if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver, never()).startRecognition_2_1(anyInt(), any(), any(), anyInt()); + } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver, + never()).startRecognition_2_3(anyInt(), any()); } } @@ -716,6 +826,7 @@ public class SoundTriggerMiddlewareImplTest { SoundTriggerModuleProperties properties = allDescriptors[0].properties; validateDefaultProperties(properties, true); + verifyNotGetProperties(); } @Test @@ -760,7 +871,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 7; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; unloadModel(module, handle, hwHandle); module.detach(); } @@ -772,7 +883,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 73; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; unloadModel(module, handle, hwHandle); module.detach(); } @@ -785,7 +896,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 7; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -806,7 +917,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 67; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -827,10 +938,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 7; - int handle = loadGenericModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Signal a capture from the driver. hwCallback.sendRecognitionEvent(hwHandle, @@ -856,10 +969,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 7; - int handle = loadPhraseModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Signal a capture from the driver. hwCallback.sendPhraseRecognitionEvent(hwHandle, @@ -892,10 +1007,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 17; - int handle = loadGenericModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Force a trigger. module.forceRecognitionEvent(handle); @@ -935,10 +1052,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 17; - int handle = loadPhraseModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Force a trigger. module.forceRecognitionEvent(handle); @@ -975,7 +1094,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 11; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1023,7 +1142,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 11; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1071,7 +1190,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 13; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1107,7 +1226,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 13; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1144,7 +1263,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 12; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; doAnswer((Answer<Void>) invocation -> { android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback @@ -1180,7 +1299,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 13; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; ModelParameterRange range = module.queryModelParameterSupport(modelHandle, ModelParameter.THRESHOLD_FACTOR); @@ -1201,7 +1320,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 13; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; doAnswer(invocation -> { android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback @@ -1234,7 +1353,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 14; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; doAnswer(invocation -> { android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback @@ -1266,7 +1385,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 17; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; when(driver.setParameter(hwHandle, android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index aaf9799de777..8a3183f7abbd 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -18,7 +18,6 @@ package com.android.server.timedetector; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -26,7 +25,6 @@ import static org.junit.Assert.fail; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; import android.icu.util.Calendar; import android.icu.util.GregorianCalendar; import android.icu.util.TimeZone; @@ -78,8 +76,7 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); - mScript.verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phoneId, timeSuggestion); } @@ -118,8 +115,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime()); mScript.simulatePhoneTimeSuggestion(timeSuggestion1) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); } @@ -146,8 +142,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime()); mScript.simulatePhoneTimeSuggestion(timeSuggestion3) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis3, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3) .assertLatestPhoneSuggestion(phoneId, timeSuggestion3); } } @@ -175,8 +170,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phone1Id, null) .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); } @@ -193,8 +187,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion); } @@ -227,8 +220,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); } } @@ -265,8 +257,7 @@ public class TimeDetectorStrategyImplTest { mScript.simulateTimePassing(); long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1); mScript.simulatePhoneTimeSuggestion(timeSuggestion1) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); // The UTC time increment should be larger than the system clock update threshold so we @@ -304,8 +295,7 @@ public class TimeDetectorStrategyImplTest { PhoneTimeSuggestion timeSuggestion4 = createPhoneTimeSuggestion(phoneId, utcTime4); mScript.simulatePhoneTimeSuggestion(timeSuggestion4) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis4, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4) .assertLatestPhoneSuggestion(phoneId, timeSuggestion4); } @@ -339,8 +329,7 @@ public class TimeDetectorStrategyImplTest { // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); // Turn off auto time detection. @@ -367,8 +356,7 @@ public class TimeDetectorStrategyImplTest { // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis2, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2) .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); } @@ -388,7 +376,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phoneSuggestion) .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectedNetworkBroadcast */) + expectedSystemClockMillis /* expectedNetworkBroadcast */) .assertLatestPhoneSuggestion(phoneId, phoneSuggestion); // Look inside and check what the strategy considers the current best phone suggestion. @@ -416,8 +404,7 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); mScript.simulateManualTimeSuggestion(timeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, false /* expectNetworkBroadcast */); + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test @@ -439,8 +426,7 @@ public class TimeDetectorStrategyImplTest { long expectedAutoClockMillis = mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedAutoClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Simulate the passage of time. @@ -463,8 +449,7 @@ public class TimeDetectorStrategyImplTest { long expectedManualClockMillis = mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime()); mScript.simulateManualTimeSuggestion(manualTimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedManualClockMillis, false /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Simulate the passage of time. @@ -475,8 +460,7 @@ public class TimeDetectorStrategyImplTest { expectedAutoClockMillis = mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); - mScript.verifySystemClockWasSetAndResetCallTracking( - expectedAutoClockMillis, true /* expectNetworkBroadcast */) + mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Switch back to manual - nothing should happen to the clock. @@ -514,8 +498,7 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); mScript.simulateNetworkTimeSuggestion(timeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, false /* expectNetworkBroadcast */); + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test @@ -550,8 +533,7 @@ public class TimeDetectorStrategyImplTest { mScript.simulateTimePassing(smallTimeIncrementMillis) .simulateNetworkTimeSuggestion(networkTimeSuggestion1) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()), - false /* expectNetworkBroadcast */); + mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime())); // Check internal state. mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null) @@ -570,8 +552,7 @@ public class TimeDetectorStrategyImplTest { mScript.simulateTimePassing(smallTimeIncrementMillis) .simulatePhoneTimeSuggestion(phoneTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()), - true /* expectNetworkBroadcast */); + mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime())); // Check internal state. mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) @@ -622,8 +603,7 @@ public class TimeDetectorStrategyImplTest { // Verify the latest network time now wins. mScript.verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()), - false /* expectNetworkTimeBroadcast */); + mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime())); // Check internal state. mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) @@ -645,7 +625,6 @@ public class TimeDetectorStrategyImplTest { // Tracking operations. private boolean mSystemClockWasSet; - private Intent mBroadcastSent; @Override public int systemClockUpdateThresholdMillis() { @@ -689,12 +668,6 @@ public class TimeDetectorStrategyImplTest { mWakeLockAcquired = false; } - @Override - public void sendStickyBroadcast(Intent intent) { - assertNotNull(intent); - mBroadcastSent = intent; - } - // Methods below are for managing the fake's behavior. void pokeSystemClockUpdateThreshold(int thresholdMillis) { @@ -739,17 +712,8 @@ public class TimeDetectorStrategyImplTest { assertEquals(expectedSystemClockMillis, mSystemClockMillis); } - void verifyIntentWasBroadcast() { - assertTrue(mBroadcastSent != null); - } - - void verifyIntentWasNotBroadcast() { - assertNull(mBroadcastSent); - } - void resetCallTracking() { mSystemClockWasSet = false; - mBroadcastSent = null; } private void assertWakeLockAcquired() { @@ -832,17 +796,12 @@ public class TimeDetectorStrategyImplTest { Script verifySystemClockWasNotSetAndResetCallTracking() { mFakeCallback.verifySystemClockNotSet(); - mFakeCallback.verifyIntentWasNotBroadcast(); mFakeCallback.resetCallTracking(); return this; } - Script verifySystemClockWasSetAndResetCallTracking( - long expectedSystemClockMillis, boolean expectNetworkBroadcast) { + Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) { mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis); - if (expectNetworkBroadcast) { - mFakeCallback.verifyIntentWasBroadcast(); - } mFakeCallback.resetCallTracking(); return this; } 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 1dd7e64690c7..6aca58f400b3 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -23,7 +23,8 @@ import static android.app.usage.UsageEvents.Event.SLICE_PINNED_PRIV; import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; @@ -501,12 +502,18 @@ public class AppStandbyControllerTests { // Can force to NEVER mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED); + REASON_MAIN_FORCED_BY_USER); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); - // Prediction can't override FORCED reason + // Prediction can't override FORCED reasons + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_FORCED_BY_SYSTEM); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_PREDICTED); + assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_FORCED); + REASON_MAIN_FORCED_BY_USER); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); @@ -631,7 +638,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; // Make sure app is in NEVER bucket mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED); + REASON_MAIN_FORCED_BY_USER); mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_NEVER); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index eb45960bc82f..bd7d9ecf84d1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -20,7 +20,6 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static org.mockito.Matchers.any; @@ -65,11 +64,10 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { NotificationChannel updatedChannel = new NotificationChannel("a", "", IMPORTANCE_HIGH); - when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false))) + when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false))) .thenReturn(updatedChannel); assertNull(extractor.process(r)); assertEquals(updatedChannel, r.getChannel()); } - } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 172df99921a0..62f52306ea49 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -112,6 +112,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -133,6 +134,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.TestablePermissions; import android.testing.TestableResources; import android.text.Html; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -475,7 +477,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled); when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled); when(mPreferencesHelper.getNotificationChannel( - anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn( mTestNotificationChannel); when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( mTestNotificationChannel.getImportance()); @@ -1762,7 +1764,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); verify(mPreferencesHelper, times(1)).getNotificationChannel( - anyString(), anyInt(), eq("foo"), anyBoolean()); + anyString(), anyInt(), eq("foo"), eq(null), anyBoolean()); } @Test @@ -1777,7 +1779,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); verify(mPreferencesHelper, times(1)).getNotificationChannel( - anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean()); + anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null), + anyBoolean()); } @Test @@ -3210,9 +3213,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {nv}, new NotificationVisibility[]{}); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {}, new NotificationVisibility[]{nv}); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); } @@ -4463,6 +4468,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testOnPanelRevealedAndHidden() { + int items = 5; + mService.mNotificationDelegate.onPanelRevealed(false, items); + verify(mAssistants, times(1)).onPanelRevealed(eq(items)); + + mService.mNotificationDelegate.onPanelHidden(); + verify(mAssistants, times(1)).onPanelHidden(); + } + + @Test public void testOnNotificationSmartReplySent() { final int replyIndex = 2; final String reply = "Hello"; @@ -5044,10 +5059,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.build(), new UserHandle(mUid), null, 0); // Make sure it has foreground service sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + sbn.getId(), sbn.getNotification(), sbn.getUserId()); waitForIdle(); // yes phone call, yes person, yes foreground service, but not allowed, no bubble @@ -5802,4 +5816,79 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mHistoryManager, times(1)).addNotification(any()); } + + @Test + public void createConversationNotificationChannel() throws Exception { + NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + original.setAllowBubbles(!original.canBubble()); + original.setShowBadge(!original.canShowBadge()); + + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel); + assertEquals(original, orig); + assertFalse(TextUtils.isEmpty(orig.getName())); + + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList( + orig))); + + mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend"); + + NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, original.getId(), "friend"); + + assertEquals(original.getName(), friendChannel.getName()); + assertEquals(original.getId(), friendChannel.getParentChannelId()); + assertEquals("friend", friendChannel.getConversationId()); + assertEquals(null, original.getConversationId()); + assertEquals(original.canShowBadge(), friendChannel.canShowBadge()); + assertEquals(original.canBubble(), friendChannel.canBubble()); + assertFalse(original.getId().equals(friendChannel.getId())); + assertNotNull(friendChannel.getId()); + } + + @Test + public void deleteConversationNotificationChannels() throws Exception { + NotificationChannel messagesParent = + new NotificationChannel("messages", "messages", IMPORTANCE_HIGH); + Parcel msgParcel = Parcel.obtain(); + messagesParent.writeToParcel(msgParcel, 0); + msgParcel.setDataPosition(0); + + NotificationChannel callsParent = + new NotificationChannel("calls", "calls", IMPORTANCE_HIGH); + Parcel callParcel = Parcel.obtain(); + callsParent.writeToParcel(callParcel, 0); + callParcel.setDataPosition(0); + + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList( + messagesParent, callsParent))); + + String conversationId = "friend"; + + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId); + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel), + conversationId); + + NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, messagesParent.getId(), conversationId); + NotificationChannel callsChild = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, callsParent.getId(), conversationId); + + assertEquals(messagesParent.getId(), messagesChild.getParentChannelId()); + assertEquals(conversationId, messagesChild.getConversationId()); + + assertEquals(callsParent.getId(), callsChild.getParentChannelId()); + assertEquals(conversationId, callsChild.getConversationId()); + + mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId); + + assertNull(mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, messagesParent.getId(), conversationId)); + assertNull(mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, callsParent.getId(), conversationId)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 8961796ed617..7173e0cb625c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -15,6 +15,7 @@ */ package com.android.server.notification; +import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; @@ -224,6 +225,26 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(expected.getGroup(), actual.getGroup()); assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes()); assertEquals(expected.getLightColor(), actual.getLightColor()); + assertEquals(expected.getParentChannelId(), actual.getParentChannelId()); + assertEquals(expected.getConversationId(), actual.getConversationId()); + } + + private void compareChannelsParentChild(NotificationChannel parent, + NotificationChannel actual, String conversationId) { + assertEquals(parent.getName(), actual.getName()); + assertEquals(parent.getDescription(), actual.getDescription()); + assertEquals(parent.shouldVibrate(), actual.shouldVibrate()); + assertEquals(parent.shouldShowLights(), actual.shouldShowLights()); + assertEquals(parent.getImportance(), actual.getImportance()); + assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility()); + assertEquals(parent.getSound(), actual.getSound()); + assertEquals(parent.canBypassDnd(), actual.canBypassDnd()); + assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern())); + assertEquals(parent.getGroup(), actual.getGroup()); + assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes()); + assertEquals(parent.getLightColor(), actual.getLightColor()); + assertEquals(parent.getId(), actual.getParentChannelId()); + assertEquals(conversationId, actual.getConversationId()); } private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) { @@ -2786,4 +2807,40 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(user10Importance, mHelper.getNotificationChannel( pkg, uidList10[0], channelId, false).getImportance()); } + + @Test + public void testGetConversationNotificationChannel() { + String conversationId = "friend"; + + NotificationChannel parent = + new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + + NotificationChannel friend = new NotificationChannel(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId), + "messages", IMPORTANCE_DEFAULT); + friend.setConversationId(parent.getId(), conversationId); + mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + + compareChannelsParentChild(parent, mHelper.getNotificationChannel( + PKG_O, UID_O, parent.getId(), conversationId, false), conversationId); + } + + @Test + public void testConversationNotificationChannelsRequireParents() { + String parentId = "does not exist"; + String conversationId = "friend"; + + NotificationChannel friend = new NotificationChannel(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId), + "messages", IMPORTANCE_DEFAULT); + friend.setConversationId(parentId, conversationId); + + try { + mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + fail("allowed creation of conversation channel without a parent"); + } catch (IllegalArgumentException e) { + // good + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 09ac9ce381c0..d819b1ada659 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -63,6 +63,26 @@ public class InsetsSourceProviderTest extends WindowTestsBase { assertEquals(Insets.of(0, 100, 0, 0), mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), false /* ignoreVisibility */)); + assertEquals(Insets.of(0, 100, 0, 0), + mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500))); + } + + @Test + public void testPostLayout_givenInsets() { + final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + ime.getFrameLw().set(0, 0, 500, 100); + ime.getGivenContentInsetsLw().set(0, 0, 0, 60); + ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75); + ime.mHasSurface = true; + mProvider.setWindow(ime, null); + mProvider.onPostLayout(); + assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame()); + assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame()); + assertEquals(Insets.of(0, 40, 0, 0), + mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */)); + assertEquals(Insets.of(0, 25, 0, 0), + mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500))); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java new file mode 100644 index 000000000000..8d2da1e6cb5b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.os.Binder; +import android.os.RemoteException; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskOrganizer}. + * + * Build/Install/Run: + * atest WmTests:TaskOrganizerTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class TaskOrganizerTests extends WindowTestsBase { + private ITaskOrganizer makeAndRegisterMockOrganizer() { + final ITaskOrganizer organizer = mock(ITaskOrganizer.class); + when(organizer.asBinder()).thenReturn(new Binder()); + + mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED); + + return organizer; + } + + @Test + public void testAppearVanish() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.removeImmediately(); + verify(organizer).taskVanished(any()); + } + + @Test + public void testSwapOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + task.setTaskOrganizer(organizer2); + verify(organizer).taskVanished(any()); + verify(organizer2).taskAppeared(any(), any()); + } + + @Test + public void testClearOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.setTaskOrganizer(null); + verify(organizer).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + } + + @Test + public void testTransferStackToOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final Task task2 = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + stack.transferToTaskOrganizer(organizer); + + verify(organizer, times(2)).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + assertTrue(task2.isControlledByTaskOrganizer()); + + stack.transferToTaskOrganizer(null); + + verify(organizer, times(2)).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + assertFalse(task2.isControlledByTaskOrganizer()); + } + + @Test + public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException { + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + task.setWindowingMode(WINDOWING_MODE_PINNED); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + verify(organizer).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + } + + @Test + public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException { + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final Task task2 = createTaskInStack(stack, 0 /* userId */); + stack.setWindowingMode(WINDOWING_MODE_PINNED); + verify(organizer, times(2)).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + assertTrue(task2.isControlledByTaskOrganizer()); + + stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + verify(organizer, times(2)).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + assertFalse(task2.isControlledByTaskOrganizer()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java deleted file mode 100644 index 9cda08458640..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.utils; - -import static android.graphics.Bitmap.Config.ARGB_8888; - -import static org.junit.Assert.assertEquals; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.ColorSpace; -import android.graphics.GraphicBuffer; -import android.graphics.Matrix; -import android.graphics.PointF; -import android.view.Surface; - -import org.junit.Before; -import org.junit.Test; - -public class RotationAnimationUtilsTest { - - private static final int BITMAP_HEIGHT = 100; - private static final int BITMAP_WIDTH = 100; - private static final int POINT_WIDTH = 1000; - private static final int POINT_HEIGHT = 2000; - - private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3); - private Matrix mMatrix; - - @Before - public void setup() { - mMatrix = new Matrix(); - } - - @Test - public void blackLuma() { - Bitmap swBitmap = createBitmap(0); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(0, borderLuma, 0); - } - - @Test - public void whiteLuma() { - Bitmap swBitmap = createBitmap(1); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(1, borderLuma, 0); - } - - @Test - public void whiteImageBlackBorderLuma() { - Bitmap swBitmap = createBitmap(1); - setBorderLuma(swBitmap, 0); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(0, borderLuma, 0); - } - - @Test - public void blackImageWhiteBorderLuma() { - Bitmap swBitmap = createBitmap(0); - setBorderLuma(swBitmap, 1); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(1, borderLuma, 0); - } - - @Test - public void rotate_0_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(POINT_WIDTH, newPoints.x, 0); - assertEquals(POINT_HEIGHT, newPoints.y, 0); - } - - @Test - public void rotate_90_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(0, newPoints.x, 0); - assertEquals(POINT_WIDTH, newPoints.y, 0); - } - - @Test - public void rotate_180_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(0, newPoints.x, 0); - assertEquals(0, newPoints.y, 0); - } - - @Test - public void rotate_270_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(POINT_HEIGHT, newPoints.x, 0); - assertEquals(0, newPoints.y, 0); - } - - private PointF checkMappedPoints(int x, int y) { - final float[] fs = new float[] {x, y}; - mMatrix.mapPoints(fs); - return new PointF(fs[0], fs[1]); - } - - private Bitmap createBitmap(float luma) { - Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, ARGB_8888); - for (int i = 0; i < BITMAP_WIDTH; i++) { - for (int j = 0; j < BITMAP_HEIGHT; j++) { - bitmap.setPixel(i, j, Color.argb(1, luma, luma, luma)); - } - } - return bitmap; - } - - private GraphicBuffer swBitmapToGraphicsBuffer(Bitmap swBitmap) { - Bitmap hwBitmap = swBitmap.copy(Bitmap.Config.HARDWARE, false); - return hwBitmap.createGraphicBufferHandle(); - } - - private void setBorderLuma(Bitmap swBitmap, float luma) { - int i; - int width = swBitmap.getWidth(); - int height = swBitmap.getHeight(); - for (i = 0; i < width; i++) { - swBitmap.setPixel(i, 0, Color.argb(1, luma, luma, luma)); - swBitmap.setPixel(i, height - 1, Color.argb(1, luma, luma, luma)); - } - for (i = 0; i < height; i++) { - swBitmap.setPixel(0, i, Color.argb(1, luma, luma, luma)); - swBitmap.setPixel(width - 1, i, Color.argb(1, luma, luma, luma)); - } - } -} diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java index f7cd6a3d2245..99065854445a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java @@ -21,12 +21,10 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; -import android.text.TextUtils; import android.util.Slog; -import java.util.Locale; +import java.io.PrintWriter; import java.util.UUID; /** @@ -39,7 +37,7 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { static final boolean DBG = false; private static final String NAME = "st_sound_model.db"; - private static final int VERSION = 1; + private static final int VERSION = 2; // Sound trigger-based sound models. public static interface GenericSoundModelContract { @@ -47,15 +45,16 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { public static final String KEY_MODEL_UUID = "model_uuid"; public static final String KEY_VENDOR_UUID = "vendor_uuid"; public static final String KEY_DATA = "data"; + public static final String KEY_MODEL_VERSION = "model_version"; } - // Table Create Statement for the sound trigger table private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE " + GenericSoundModelContract.TABLE + "(" + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY," + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT," - + GenericSoundModelContract.KEY_DATA + " BLOB" + " )"; + + GenericSoundModelContract.KEY_DATA + " BLOB," + + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER" + " )"; public SoundTriggerDbHelper(Context context) { @@ -70,9 +69,13 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // TODO: For now, drop older tables and recreate new ones. - db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE); - onCreate(db); + if (oldVersion == 1) { + // In version 2, a model version number was added. + Slog.d(TAG, "Adding model version column"); + db.execSQL("ALTER TABLE " + GenericSoundModelContract.TABLE + " ADD COLUMN " + + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1"); + oldVersion++; + } } /** @@ -86,6 +89,7 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString()); values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString()); values.put(GenericSoundModelContract.KEY_DATA, soundModel.data); + values.put(GenericSoundModelContract.KEY_MODEL_VERSION, soundModel.version); try { return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values, @@ -113,8 +117,10 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { GenericSoundModelContract.KEY_DATA)); String vendor_uuid = c.getString( c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID)); + int version = c.getInt( + c.getColumnIndex(GenericSoundModelContract.KEY_MODEL_VERSION)); return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid), - data); + data, version); } while (c.moveToNext()); } } finally { @@ -142,4 +148,48 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { } } } + + public void dump(PrintWriter pw) { + synchronized(this) { + String selectQuery = "SELECT * FROM " + GenericSoundModelContract.TABLE; + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); + try { + pw.println(" Enrolled GenericSoundModels:"); + if (c.moveToFirst()) { + String[] columnNames = c.getColumnNames(); + do { + for (String name : columnNames) { + int colNameIndex = c.getColumnIndex(name); + int type = c.getType(colNameIndex); + switch (type) { + case Cursor.FIELD_TYPE_STRING: + pw.printf(" %s: %s\n", name, + c.getString(colNameIndex)); + break; + case Cursor.FIELD_TYPE_BLOB: + pw.printf(" %s: data blob\n", name); + break; + case Cursor.FIELD_TYPE_INTEGER: + pw.printf(" %s: %d\n", name, + c.getInt(colNameIndex)); + break; + case Cursor.FIELD_TYPE_FLOAT: + pw.printf(" %s: %f\n", name, + c.getFloat(colNameIndex)); + break; + case Cursor.FIELD_TYPE_NULL: + pw.printf(" %s: null\n", name); + break; + } + } + pw.println(); + } while (c.moveToNext()); + } + } finally { + c.close(); + db.close(); + } + } + } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 198b4c31249a..bde2cfd52c0f 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -611,63 +611,89 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { int setParameter(UUID modelId, @ModelParams int modelParam, int value) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_set_parameter", 1); - if (modelId == null || mModule == null) { - return SoundTrigger.STATUS_ERROR; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "SetParameter: Invalid model id:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } + return setParameterLocked(mModelDataMap.get(modelId), modelParam, value); + } + } - return mModule.setParameter(modelData.getHandle(), modelParam, value); + int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) { + synchronized (mLock) { + return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value); } } - int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam, + int value) { + MetricsLogger.count(mContext, "sth_set_parameter", 1); + if (mModule == null) { + return SoundTrigger.STATUS_NO_INIT; + } + if (modelData == null || !modelData.isModelLoaded()) { + Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData); + return SoundTrigger.STATUS_BAD_VALUE; + } + + return mModule.setParameter(modelData.getHandle(), modelParam, value); + } + + int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_get_parameter", 1); - if (mModule == null) { - throw new UnsupportedOperationException("SoundTriggerModule not initialized"); - } + return getParameterLocked(mModelDataMap.get(modelId), modelParam); + } + } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - throw new IllegalArgumentException("Invalid model id:" + modelId); - } - if (!modelData.isModelLoaded()) { - throw new UnsupportedOperationException("Given model is not loaded:" + modelId); - } + int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } - return mModule.getParameter(modelData.getHandle(), modelParam); + private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_get_parameter", 1); + if (mModule == null) { + throw new UnsupportedOperationException("SoundTriggerModule not initialized"); } + + if (modelData == null) { + throw new IllegalArgumentException("Invalid model id"); + } + if (!modelData.isModelLoaded()) { + throw new UnsupportedOperationException("Given model is not loaded:" + modelData); + } + + return mModule.getParameter(modelData.getHandle(), modelParam); } @Nullable ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_query_parameter", 1); - if (mModule == null) { - return null; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "queryParameter: Invalid model id:" + modelId); - return null; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId); - return null; - } + return queryParameterLocked(mModelDataMap.get(modelId), modelParam); + } + } - return mModule.queryParameter(modelData.getHandle(), modelParam); + @Nullable + ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@Nullable ModelData modelData, + @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_query_parameter", 1); + if (mModule == null) { + return null; + } + if (modelData == null) { + Slog.w(TAG, "queryParameter: Invalid model id"); + return null; } + if (!modelData.isModelLoaded()) { + Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData); + return null; + } + + return mModule.queryParameter(modelData.getHandle(), modelParam); } //---- SoundTrigger.StatusListener methods diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java index d05e044499ab..54dffdc4c13a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java @@ -16,17 +16,17 @@ package com.android.server.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.Keyphrase; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; -import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; -import android.hardware.soundtrigger.SoundTriggerModule; + +import com.android.server.voiceinteraction.VoiceInteractionManagerService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -71,6 +71,58 @@ public abstract class SoundTriggerInternal { public abstract ModuleProperties getModuleProperties(); /** + * Set a model specific {@link ModelParams} with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value); + + /** + * Get a model specific {@link ModelParams}. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See ModelParams for parameter default values. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return value of parameter + * @throws UnsupportedOperationException if hal or model do not support this API. + * queryParameter should be checked first. + * @throws IllegalArgumentException if invalid model handle or parameter is passed. + * queryParameter should be checked first. + */ + public abstract int getParameter(int keyphraseId, @ModelParams int modelParam); + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter} + * or {@link SoundTriggerInternal#getParameter}. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public abstract ModelParamRange queryParameter(int keyphraseId, + @ModelParams int modelParam); + + /** * Unloads (and stops if running) the given keyphraseId */ public abstract int unloadKeyphraseModel(int keyphaseId); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 68b16f39e149..767010ae821a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1446,26 +1446,45 @@ public class SoundTriggerService extends SystemService { @Override public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener, recognitionConfig); } @Override public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener); } @Override public ModuleProperties getModuleProperties() { - if (!isInitialized()) return null; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.getModuleProperties(); } @Override + public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value); + } + + @Override + public int getParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam); + } + + @Override + @Nullable + public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam); + } + + @Override public int unloadKeyphraseModel(int keyphraseId) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId); } @@ -1476,6 +1495,9 @@ public class SoundTriggerService extends SystemService { // log sEventLogger.dump(pw); + // enrolled models + mDbHelper.dump(pw); + // stats mSoundModelStatTracker.dump(pw); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index dd7b5a8752e5..c58b6da64baa 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -27,6 +27,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.text.TextUtils; import android.util.Slog; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -43,7 +44,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { static final boolean DBG = false; private static final String NAME = "sound_model.db"; - private static final int VERSION = 6; + private static final int VERSION = 7; public static interface SoundModelContract { public static final String TABLE = "sound_model"; @@ -56,6 +57,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { public static final String KEY_LOCALE = "locale"; public static final String KEY_HINT_TEXT = "hint_text"; public static final String KEY_USERS = "users"; + public static final String KEY_MODEL_VERSION = "model_version"; } // Table Create Statement @@ -70,6 +72,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { + SoundModelContract.KEY_LOCALE + " TEXT," + SoundModelContract.KEY_HINT_TEXT + " TEXT," + SoundModelContract.KEY_USERS + " TEXT," + + SoundModelContract.KEY_MODEL_VERSION + " INTEGER," + "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + "," + SoundModelContract.KEY_LOCALE + "," + SoundModelContract.KEY_USERS + ")" @@ -138,6 +141,13 @@ public class DatabaseHelper extends SQLiteOpenHelper { } oldVersion++; } + if (oldVersion == 6) { + // In version 7, a model version number was added. + Slog.d(TAG, "Adding model version column"); + db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN " + + SoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1"); + oldVersion++; + } } /** @@ -155,6 +165,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); values.put(SoundModelContract.KEY_DATA, soundModel.data); + values.put(SoundModelContract.KEY_MODEL_VERSION, soundModel.version); if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) { values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id); @@ -250,6 +261,8 @@ public class DatabaseHelper extends SQLiteOpenHelper { c.getColumnIndex(SoundModelContract.KEY_LOCALE)); String text = c.getString( c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); + int version = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION)); // Only add keyphrases meant for the current user. if (users == null) { @@ -282,7 +295,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { vendorUuid = UUID.fromString(vendorUuidString); } KeyphraseSoundModel model = new KeyphraseSoundModel( - UUID.fromString(modelUuid), vendorUuid, data, keyphrases); + UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version); if (DBG) { Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " + model); @@ -325,6 +338,10 @@ public class DatabaseHelper extends SQLiteOpenHelper { return users; } + /** + * SoundModelRecord is no longer used, and it should only be used on database migration. + * This class does not need to be modified when modifying the database scheme. + */ private static class SoundModelRecord { public final String modelUuid; public final String vendorUuid; @@ -413,4 +430,48 @@ public class DatabaseHelper extends SQLiteOpenHelper { return a == b; } } + + public void dump(PrintWriter pw) { + synchronized(this) { + String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); + try { + pw.println(" Enrolled KeyphraseSoundModels:"); + if (c.moveToFirst()) { + String[] columnNames = c.getColumnNames(); + do { + for (String name : columnNames) { + int colNameIndex = c.getColumnIndex(name); + int type = c.getType(colNameIndex); + switch (type) { + case Cursor.FIELD_TYPE_STRING: + pw.printf(" %s: %s\n", name, + c.getString(colNameIndex)); + break; + case Cursor.FIELD_TYPE_BLOB: + pw.printf(" %s: data blob\n", name); + break; + case Cursor.FIELD_TYPE_INTEGER: + pw.printf(" %s: %d\n", name, + c.getInt(colNameIndex)); + break; + case Cursor.FIELD_TYPE_FLOAT: + pw.printf(" %s: %f\n", name, + c.getFloat(colNameIndex)); + break; + case Cursor.FIELD_TYPE_NULL: + pw.printf(" %s: null\n", name); + break; + } + } + pw.println(); + } while (c.moveToNext()); + } + } finally { + c.close(); + db.close(); + } + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index e9db31ba3e25..506c67e12528 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -41,7 +41,9 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.os.Binder; @@ -157,9 +159,13 @@ public class VoiceInteractionManagerService extends SystemService { } } + private boolean isSupported(UserInfo user) { + return user.isFull(); + } + @Override - public boolean isSupported(UserInfo userInfo) { - return userInfo.isFull(); + public boolean isSupportedUser(TargetUser user) { + return isSupported(user.getUserInfo()); } @Override @@ -1084,6 +1090,55 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public int setParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam, int value) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + public int getParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.getParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + @Nullable + public ModelParamRange queryParameter(IVoiceInteractionService service, + int keyphraseId, @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + private synchronized void unloadAllKeyphraseModels() { for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) { final long caller = Binder.clearCallingIdentity(); @@ -1292,6 +1347,7 @@ public class VoiceInteractionManagerService extends SystemService { pw.println(" mCurUserUnlocked: " + mCurUserUnlocked); pw.println(" mCurUserSupported: " + mCurUserSupported); dumpSupportedUsers(pw, " "); + mDbHelper.dump(pw); if (mImpl == null) { pw.println(" (No active implementation)"); return; diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING new file mode 100644 index 000000000000..d58566673eec --- /dev/null +++ b/telecomm/TEST_MAPPING @@ -0,0 +1,29 @@ +{ + "presubmit": [ + { + "name": "TeleServiceTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelecomUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelephonyProviderTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} + diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING new file mode 100644 index 000000000000..d58566673eec --- /dev/null +++ b/telephony/TEST_MAPPING @@ -0,0 +1,29 @@ +{ + "presubmit": [ + { + "name": "TeleServiceTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelecomUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelephonyProviderTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} + diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index aaafee29e24a..f39981fdf25d 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -16,8 +16,6 @@ package android.telephony; -import com.android.telephony.Rlog; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 368f8f1dab2e..9bc534c2877a 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -18,17 +18,19 @@ package com.android.internal.telephony; import android.annotation.Nullable; import android.content.ContentResolver; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.RemoteException; +import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; -import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -76,15 +78,16 @@ public final class CarrierAppUtils { */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, - TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) { + TelephonyManager telephonyManager, int userId, Context context) { if (DEBUG) { - Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } SystemConfig config = SystemConfig.getInstance(); ArraySet<String> systemCarrierAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierApps(); ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); @@ -102,10 +105,10 @@ public final class CarrierAppUtils { * Manager can kill it, and this can lead to crashes as the app is in an unexpected state. */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, IPermissionManager permissionManager, - ContentResolver contentResolver, int userId) { + IPackageManager packageManager, IPermissionManager permissionManager, int userId, + Context context) { if (DEBUG) { - Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } SystemConfig config = SystemConfig.getInstance(); ArraySet<String> systemCarrierAppsDisabledUntilUsed = @@ -114,15 +117,23 @@ public final class CarrierAppUtils { ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, null /* telephonyManager */, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); } + private static ContentResolver getContentResolverForUser(Context context, int userId) { + Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId), + 0); + return userContext.getContentResolver(); + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. */ + // Must be public b/c framework unit tests can't access package-private methods. @VisibleForTesting public static void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, @@ -130,8 +141,8 @@ public final class CarrierAppUtils { ContentResolver contentResolver, int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) { - List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager, - userId, systemCarrierAppsDisabledUntilUsed); + List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + packageManager, userId, systemCarrierAppsDisabledUntilUsed); if (candidates == null || candidates.isEmpty()) { return; } @@ -142,9 +153,8 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed); List<String> enabledCarrierPackages = new ArrayList<>(); - - boolean hasRunOnce = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1; + boolean hasRunOnce = Settings.Secure.getInt(contentResolver, + Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1; try { for (ApplicationInfo ai : candidates) { @@ -168,16 +178,17 @@ public final class CarrierAppUtils { } } + int enabledSetting = packageManager.getApplicationEnabledSetting(packageName, + userId); if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && (ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || ai.enabledSetting + || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED - || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { - Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user " + || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { + Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( packageName, @@ -194,13 +205,16 @@ public final class CarrierAppUtils { // Also enable any associated apps for this carrier app. if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || associatedApp.enabledSetting + || associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { - Rlog.i(TAG, "Update associated state(" + associatedApp.packageName + Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( associatedApp.packageName, @@ -221,11 +235,10 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Rlog.i(TAG, "Update state(" + packageName + Log.i(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED for user " + userId); packageManager.setSystemAppInstallState( packageName, @@ -239,11 +252,14 @@ public final class CarrierAppUtils { if (!hasRunOnce) { if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Rlog.i(TAG, + Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): DISABLED_UNTIL_USED for user " + userId); packageManager.setSystemAppInstallState( @@ -259,8 +275,7 @@ public final class CarrierAppUtils { // Mark the execution so we do not disable apps again. if (!hasRunOnce) { - Settings.Secure.putIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId); + Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1); } if (!enabledCarrierPackages.isEmpty()) { @@ -271,7 +286,7 @@ public final class CarrierAppUtils { permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId); } } catch (RemoteException e) { - Rlog.w(TAG, "Could not reach PackageManager", e); + Log.w(TAG, "Could not reach PackageManager", e); } } @@ -351,6 +366,31 @@ public final class CarrierAppUtils { return apps; } + private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper( + IPackageManager packageManager, + int userId, + ArraySet<String> systemCarrierAppsDisabledUntilUsed) { + if (systemCarrierAppsDisabledUntilUsed == null) { + return null; + } + + int size = systemCarrierAppsDisabledUntilUsed.size(); + if (size == 0) { + return null; + } + + List<ApplicationInfo> apps = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); + ApplicationInfo ai = + getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName); + if (ai != null) { + apps.add(ai); + } + } + return apps; + } + private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( IPackageManager packageManager, int userId, @@ -363,11 +403,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfSystemApp( + getApplicationInfoIfNotUpdatedSystemApp( packageManager, userId, associatedAppPackages.get(j)); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null && !ai.isUpdatedSystemApp()) { + if (ai != null) { List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -381,6 +421,26 @@ public final class CarrierAppUtils { } @Nullable + private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( + IPackageManager packageManager, + int userId, + String packageName) { + try { + ApplicationInfo ai = packageManager.getApplicationInfo(packageName, + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_FACTORY_ONLY, userId); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + Log.w(TAG, "Could not reach PackageManager", e); + } + return null; + } + + @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( IPackageManager packageManager, int userId, @@ -388,12 +448,13 @@ public final class CarrierAppUtils { try { ApplicationInfo ai = packageManager.getApplicationInfo(packageName, PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId); - if (ai != null && ai.isSystemApp()) { + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY, userId); + if (ai != null) { return ai; } } catch (RemoteException e) { - Rlog.w(TAG, "Could not reach PackageManager", e); + Log.w(TAG, "Could not reach PackageManager", e); } return null; } diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index a36ff9341275..5c53f7e5a4d0 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import com.android.telephony.Rlog; +import android.util.Log; import android.text.TextUtils; import android.util.SparseIntArray; @@ -498,11 +498,11 @@ public class GsmAlphabet { StringBuilder ret = new StringBuilder(lengthSeptets); if (languageTable < 0 || languageTable > sLanguageTables.length) { - Rlog.w(TAG, "unknown language table " + languageTable + ", using default"); + Log.w(TAG, "unknown language table " + languageTable + ", using default"); languageTable = 0; } if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) { - Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default"); + Log.w(TAG, "unknown single shift table " + shiftTable + ", using default"); shiftTable = 0; } @@ -512,11 +512,11 @@ public class GsmAlphabet { String shiftTableToChar = sLanguageShiftTables[shiftTable]; if (languageTableToChar.isEmpty()) { - Rlog.w(TAG, "no language table for code " + languageTable + ", using default"); + Log.w(TAG, "no language table for code " + languageTable + ", using default"); languageTableToChar = sLanguageTables[0]; } if (shiftTableToChar.isEmpty()) { - Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default"); + Log.w(TAG, "no single shift table for code " + shiftTable + ", using default"); shiftTableToChar = sLanguageShiftTables[0]; } @@ -556,7 +556,7 @@ public class GsmAlphabet { } } } catch (RuntimeException ex) { - Rlog.e(TAG, "Error GSM 7 bit packed: ", ex); + Log.e(TAG, "Error GSM 7 bit packed: ", ex); return null; } @@ -813,7 +813,7 @@ public class GsmAlphabet { for (int i = 0; i < sz; i++) { char c = s.charAt(i); if (c == GSM_EXTENDED_ESCAPE) { - Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping."); + Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping."); continue; } if (charToLanguageTable.get(c, -1) != -1) { @@ -892,7 +892,7 @@ public class GsmAlphabet { for (int i = 0; i < sz && !lpcList.isEmpty(); i++) { char c = s.charAt(i); if (c == GSM_EXTENDED_ESCAPE) { - Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!"); + Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!"); continue; } // iterate through enabled locking shift tables @@ -1496,7 +1496,7 @@ public class GsmAlphabet { int numTables = sLanguageTables.length; int numShiftTables = sLanguageShiftTables.length; if (numTables != numShiftTables) { - Rlog.e(TAG, "Error: language tables array length " + numTables + + Log.e(TAG, "Error: language tables array length " + numTables + " != shift tables array length " + numShiftTables); } @@ -1506,7 +1506,7 @@ public class GsmAlphabet { int tableLen = table.length(); if (tableLen != 0 && tableLen != 128) { - Rlog.e(TAG, "Error: language tables index " + i + + Log.e(TAG, "Error: language tables index " + i + " length " + tableLen + " (expected 128 or 0)"); } @@ -1524,7 +1524,7 @@ public class GsmAlphabet { int shiftTableLen = shiftTable.length(); if (shiftTableLen != 0 && shiftTableLen != 128) { - Rlog.e(TAG, "Error: language shift tables index " + i + + Log.e(TAG, "Error: language shift tables index " + i + " length " + shiftTableLen + " (expected 128 or 0)"); } diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java index 45a563c09394..714f5a673633 100644 --- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import com.android.telephony.Rlog; +import android.util.Log; import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; import com.android.internal.telephony.HbpcdLookup.MccIdd; @@ -54,16 +54,16 @@ public final class HbpcdUtils { if (c2 != null) { int c2Counter = c2.getCount(); if (DBG) { - Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter); + Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter); } if (c2Counter == 1) { if (DBG) { - Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2); + Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2); } c2.moveToFirst(); tmpMcc = c2.getInt(0); if (DBG) { - Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc); + Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc); } c2.close(); return tmpMcc; @@ -85,18 +85,18 @@ public final class HbpcdUtils { int c3Counter = c3.getCount(); if (c3Counter > 0) { if (c3Counter > 1) { - Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3); + Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3); } - if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3); + if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3); c3.moveToFirst(); tmpMcc = c3.getInt(0); if (DBG) { - Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc); + Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc); } if (!isNitzTimeZone) { // time zone is not accurate, it may get wrong mcc, ignore it. if (DBG) { - Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc); + Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc); } tmpMcc = 0; } @@ -115,18 +115,18 @@ public final class HbpcdUtils { null, null); if (c5 != null) { if (c5.getCount() > 0) { - if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5); + if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5); c5.moveToFirst(); tmpMcc = c5.getInt(0); - if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc); + if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc); c5.close(); return tmpMcc; } c5.close(); } - if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range."); + if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range."); - if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc); + if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc); // If unknown MCC still could not be resolved, return tmpMcc; } @@ -135,7 +135,7 @@ public final class HbpcdUtils { * Gets country information with given MCC. */ public String getIddByMcc(int mcc) { - if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC."); + if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC."); String idd = ""; Cursor c = null; @@ -145,19 +145,19 @@ public final class HbpcdUtils { MccIdd.MCC + "=" + mcc, null, null); if (cur != null) { if (cur.getCount() > 0) { - if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur); + if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur); // TODO: for those country having more than 1 IDDs, need more information // to decide which IDD would be used. currently just use the first 1. cur.moveToFirst(); idd = cur.getString(0); - if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd); + if (DBG) Log.d(LOG_TAG, "IDD = " + idd); } cur.close(); } if (c != null) c.close(); - if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC."); + if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC."); return idd; } } diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index afb9b6f52bdb..9b8282806c3c 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -40,7 +40,7 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import com.android.telephony.Rlog; +import android.util.Log; import android.telephony.TelephonyManager; import android.util.Log; @@ -565,7 +565,7 @@ public final class SmsApplication { int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid, applicationData.mPackageName); if (mode != AppOpsManager.MODE_ALLOWED) { - Rlog.e(LOG_TAG, applicationData.mPackageName + " lost " + Log.e(LOG_TAG, applicationData.mPackageName + " lost " + opStr + ": " + (updateIfNeeded ? " (fixing)" : " (no permission to fix)")); if (updateIfNeeded) { @@ -643,7 +643,7 @@ public final class SmsApplication { int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid; setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT); } catch (NameNotFoundException e) { - Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); + Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); } } @@ -750,7 +750,7 @@ public final class SmsApplication { // the package signature matches system signature. final int result = packageManager.checkSignatures(context.getPackageName(), packageName); if (result != PackageManager.SIGNATURE_MATCH) { - Rlog.e(LOG_TAG, packageName + " does not have system signature"); + Log.e(LOG_TAG, packageName + " does not have system signature"); return; } try { @@ -758,13 +758,13 @@ public final class SmsApplication { int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid, packageName); if (mode != AppOpsManager.MODE_ALLOWED) { - Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); + Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); setExclusiveAppops(packageName, appOps, info.applicationInfo.uid, AppOpsManager.MODE_ALLOWED); } } catch (NameNotFoundException e) { // No whitelisted system app on this device - Rlog.e(LOG_TAG, "Package not found: " + packageName); + Log.e(LOG_TAG, "Package not found: " + packageName); } } diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index 06c37288a1a6..cd365a113189 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -24,13 +24,16 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; import com.android.internal.telephony.HbpcdLookup.MccIdd; import com.android.internal.telephony.HbpcdLookup.MccLookup; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; @@ -143,7 +146,7 @@ public class SmsNumberUtils { // First check whether the number is a NANP number. int nanpState = checkNANP(numberEntry, allIDDs); - if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState)); + if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState)); if ((nanpState == NP_NANP_LOCAL) || (nanpState == NP_NANP_AREA_LOCAL) @@ -173,7 +176,7 @@ public class SmsNumberUtils { int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs, NANP_IDD); - if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState)); + if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState)); String returnNumber = null; switch (internationalState) { @@ -272,7 +275,7 @@ public class SmsNumberUtils { } } } catch (SQLException e) { - Rlog.e(TAG, "Can't access HbpcdLookup database", e); + Log.e(TAG, "Can't access HbpcdLookup database", e); } finally { if (cursor != null) { cursor.close(); @@ -281,7 +284,7 @@ public class SmsNumberUtils { IDDS_MAPS.put(mcc, allIDDs); - if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs); + if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs); return allIDDs; } @@ -472,7 +475,7 @@ public class SmsNumberUtils { int tempCC = allCCs[i]; for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) { if (tempCC == ccArray[j]) { - if (DBG) Rlog.d(TAG, "Country code = " + tempCC); + if (DBG) Log.d(TAG, "Country code = " + tempCC); return tempCC; } } @@ -509,7 +512,7 @@ public class SmsNumberUtils { } } } catch (SQLException e) { - Rlog.e(TAG, "Can't access HbpcdLookup database", e); + Log.e(TAG, "Can't access HbpcdLookup database", e); } finally { if (cursor != null) { cursor.close(); @@ -561,10 +564,10 @@ public class SmsNumberUtils { * Filter the destination number if using VZW sim card. */ public static String filterDestAddr(Context context, int subId, String destAddr) { - if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" ); + if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" ); if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) { - Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) + + Log.w(TAG, "destAddr" + pii(TAG, destAddr) + " is not a global phone number! Nothing changed."); return destAddr; } @@ -585,9 +588,9 @@ public class SmsNumberUtils { } if (DBG) { - Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted.")); - Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG, - result) : Rlog.pii(TAG, destAddr)) + "\""); + Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted.")); + Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG, + result) : pii(TAG, destAddr)) + "\""); } return result != null ? result : destAddr; } @@ -608,7 +611,7 @@ public class SmsNumberUtils { networkType = CDMA_HOME_NETWORK; } } else { - if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType); + if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType); } return networkType; @@ -650,4 +653,44 @@ public class SmsNumberUtils { // by default this value is false return false; } + + /** + * Redact personally identifiable information for production users. + * @param tag used to identify the source of a log message + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If tag is loggable in verbose mode or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + private static String pii(String tag, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Returns a secure hash (using the SHA1 algorithm) of the provided input. + * + * @return "****" if the build type is user, otherwise the hash + * @param input the bytes for which the secure hash should be computed. + */ + private static String secureHash(byte[] input) { + // Refrain from logging user personal information in user build. + if (android.os.Build.IS_USER) { + return "****"; + } + + MessageDigest messageDigest; + + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return "####"; + } + + byte[] result = messageDigest.digest(input); + return Base64.encodeToString( + result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } } diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 4109ca6bd7d0..f6ce0dc827d8 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -28,7 +28,6 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import com.android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; @@ -521,7 +520,7 @@ public final class TelephonyPermissions { return; } - if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next."); + if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next."); enforceCallingOrSelfCarrierPrivilege(context, subId, message); } @@ -539,7 +538,7 @@ public final class TelephonyPermissions { } if (DBG) { - Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); + Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); } enforceCallingOrSelfCarrierPrivilege(context, subId, message); @@ -559,7 +558,7 @@ public final class TelephonyPermissions { } if (DBG) { - Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + Log.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + "check carrier privilege next."); } @@ -584,7 +583,7 @@ public final class TelephonyPermissions { Context context, int subId, int uid, String message) { if (getCarrierPrivilegeStatus(context, subId, uid) != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { - if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege."); + if (DBG) Log.e(LOG_TAG, "No Carrier Privilege."); throw new SecurityException(message); } } diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java index e01deb2802ff..1e1cdba70ad0 100644 --- a/telephony/java/android/telephony/CallQuality.java +++ b/telephony/java/android/telephony/CallQuality.java @@ -80,6 +80,9 @@ public final class CallQuality implements Parcelable { private int mMaxRelativeJitter; private int mAverageRoundTripTime; private int mCodecType; + private boolean mRtpInactivityDetected; + private boolean mRxSilenceDetected; + private boolean mTxSilenceDetected; /** @hide **/ public CallQuality(Parcel in) { @@ -94,6 +97,9 @@ public final class CallQuality implements Parcelable { mMaxRelativeJitter = in.readInt(); mAverageRoundTripTime = in.readInt(); mCodecType = in.readInt(); + mRtpInactivityDetected = in.readBoolean(); + mRxSilenceDetected = in.readBoolean(); + mTxSilenceDetected = in.readBoolean(); } /** @hide **/ @@ -109,7 +115,7 @@ public final class CallQuality implements Parcelable { * @param numRtpPacketsReceived RTP packets received from network * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never * transmitted - * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved + * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received * @param averageRelativeJitter average relative jitter in milliseconds * @param maxRelativeJitter maximum relative jitter in milliseconds * @param averageRoundTripTime average round trip delay in milliseconds @@ -127,6 +133,48 @@ public final class CallQuality implements Parcelable { int maxRelativeJitter, int averageRoundTripTime, int codecType) { + this(downlinkCallQualityLevel, uplinkCallQualityLevel, callDuration, + numRtpPacketsTransmitted, numRtpPacketsReceived, numRtpPacketsTransmittedLost, + numRtpPacketsNotReceived, averageRelativeJitter, maxRelativeJitter, + averageRoundTripTime, codecType, false, false, false); + } + + /** + * Constructor. + * + * @param callQualityLevel the call quality level (see #CallQualityLevel) + * @param callDuration the call duration in milliseconds + * @param numRtpPacketsTransmitted RTP packets sent to network + * @param numRtpPacketsReceived RTP packets received from network + * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never + * transmitted + * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received + * @param averageRelativeJitter average relative jitter in milliseconds + * @param maxRelativeJitter maximum relative jitter in milliseconds + * @param averageRoundTripTime average round trip delay in milliseconds + * @param codecType the codec type + * @param rtpInactivityDetected True if no incoming RTP is received for a continuous duration of + * 4 seconds + * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds + * immediately after call is connected + * @param txSilenceDetected True if only silence RTP packets are sent for 20 seconds immediately + * after call is connected + */ + public CallQuality( + @CallQualityLevel int downlinkCallQualityLevel, + @CallQualityLevel int uplinkCallQualityLevel, + int callDuration, + int numRtpPacketsTransmitted, + int numRtpPacketsReceived, + int numRtpPacketsTransmittedLost, + int numRtpPacketsNotReceived, + int averageRelativeJitter, + int maxRelativeJitter, + int averageRoundTripTime, + int codecType, + boolean rtpInactivityDetected, + boolean rxSilenceDetected, + boolean txSilenceDetected) { this.mDownlinkCallQualityLevel = downlinkCallQualityLevel; this.mUplinkCallQualityLevel = uplinkCallQualityLevel; this.mCallDuration = callDuration; @@ -138,6 +186,9 @@ public final class CallQuality implements Parcelable { this.mMaxRelativeJitter = maxRelativeJitter; this.mAverageRoundTripTime = averageRoundTripTime; this.mCodecType = codecType; + this.mRtpInactivityDetected = rtpInactivityDetected; + this.mRxSilenceDetected = rxSilenceDetected; + this.mTxSilenceDetected = txSilenceDetected; } // getters @@ -226,6 +277,29 @@ public final class CallQuality implements Parcelable { } /** + * Returns true if no rtp packets are received continuously for the last 4 seconds + */ + public boolean isRtpInactivityDetected() { + return mRtpInactivityDetected; + } + + /** + * Returns true if only silence rtp packets are received for a duration of 20 seconds starting + * at call setup + */ + public boolean isIncomingSilenceDetected() { + return mRxSilenceDetected; + } + + /** + * Returns true if only silence rtp packets are sent for a duration of 20 seconds starting at + * call setup + */ + public boolean isOutgoingSilenceDetected() { + return mTxSilenceDetected; + } + + /** * Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in * {@link ImsStreamMediaProfile}. * @@ -270,6 +344,9 @@ public final class CallQuality implements Parcelable { + " maxRelativeJitter=" + mMaxRelativeJitter + " averageRoundTripTime=" + mAverageRoundTripTime + " codecType=" + mCodecType + + " rtpInactivityDetected=" + mRtpInactivityDetected + + " txSilenceDetected=" + mRxSilenceDetected + + " rxSilenceDetected=" + mTxSilenceDetected + "}"; } @@ -286,7 +363,10 @@ public final class CallQuality implements Parcelable { mAverageRelativeJitter, mMaxRelativeJitter, mAverageRoundTripTime, - mCodecType); + mCodecType, + mRtpInactivityDetected, + mRxSilenceDetected, + mTxSilenceDetected); } @Override @@ -311,7 +391,10 @@ public final class CallQuality implements Parcelable { && mAverageRelativeJitter == s.mAverageRelativeJitter && mMaxRelativeJitter == s.mMaxRelativeJitter && mAverageRoundTripTime == s.mAverageRoundTripTime - && mCodecType == s.mCodecType); + && mCodecType == s.mCodecType + && mRtpInactivityDetected == s.mRtpInactivityDetected + && mRxSilenceDetected == s.mRxSilenceDetected + && mTxSilenceDetected == s.mTxSilenceDetected); } /** @@ -336,6 +419,9 @@ public final class CallQuality implements Parcelable { dest.writeInt(mMaxRelativeJitter); dest.writeInt(mAverageRoundTripTime); dest.writeInt(mCodecType); + dest.writeBoolean(mRtpInactivityDetected); + dest.writeBoolean(mRxSilenceDetected); + dest.writeBoolean(mTxSilenceDetected); } public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6a622378dac7..86178334879f 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -672,6 +672,12 @@ public class CarrierConfigManager { "carrier_promote_wfc_on_call_fail_bool"; /** + * Flag specifying whether provisioning is required for RCS. + */ + public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = + "carrier_rcs_provisioning_required_bool"; + + /** * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi * Calling. */ @@ -848,9 +854,12 @@ public class CarrierConfigManager { "carrier_force_disable_etws_cmas_test_bool"; /** - * The default flag specifying whether "Turn on Notifications" option will be always shown in - * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not. + * The default flag specifying whether "Allow alerts" option will be always shown in + * emergency alerts settings regardless developer options is turned on or not. + * + * @deprecated The allow alerts option is always shown now. No longer need a config for that. */ + @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; @@ -1627,8 +1636,8 @@ public class CarrierConfigManager { * Defines carrier-specific actions which act upon * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED * and configured signal args: - * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType}, - * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode} + * {@link TelephonyManager#EXTRA_APN_TYPE apnType}, + * {@link TelephonyManager#EXTRA_ERROR_CODE errorCode} * used for customization of the default carrier app * Format: * { @@ -3497,6 +3506,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2); sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2); sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false); diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java index 8e703fee3126..3f0aeb54f132 100644 --- a/telephony/java/android/telephony/CellIdentity.java +++ b/telephony/java/android/telephony/CellIdentity.java @@ -16,16 +16,17 @@ package android.telephony; -import com.android.telephony.Rlog; - import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.hardware.radio.V1_0.CellInfoType; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.telephony.Rlog; + import java.util.Objects; import java.util.UUID; @@ -324,6 +325,86 @@ public abstract class CellIdentity implements Parcelable { } /** @hide */ + public static CellIdentity create(android.hardware.radio.V1_0.CellIdentity cellIdentity) { + if (cellIdentity == null) return null; + switch(cellIdentity.cellInfoType) { + case CellInfoType.GSM: { + if (cellIdentity.cellIdentityGsm.size() == 1) { + return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0)); + } + break; + } + case CellInfoType.WCDMA: { + if (cellIdentity.cellIdentityWcdma.size() == 1) { + return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0)); + } + break; + } + case CellInfoType.TD_SCDMA: { + if (cellIdentity.cellIdentityTdscdma.size() == 1) { + return new CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0)); + } + break; + } + case CellInfoType.LTE: { + if (cellIdentity.cellIdentityLte.size() == 1) { + return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0)); + } + break; + } + case CellInfoType.CDMA: { + if (cellIdentity.cellIdentityCdma.size() == 1) { + return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0)); + } + break; + } + case CellInfoType.NONE: break; + default: break; + } + return null; + } + + /** @hide */ + public static CellIdentity create(android.hardware.radio.V1_2.CellIdentity cellIdentity) { + if (cellIdentity == null) return null; + switch(cellIdentity.cellInfoType) { + case CellInfoType.GSM: { + if (cellIdentity.cellIdentityGsm.size() == 1) { + return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0)); + } + break; + } + case CellInfoType.WCDMA: { + if (cellIdentity.cellIdentityWcdma.size() == 1) { + return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0)); + } + break; + } + case CellInfoType.TD_SCDMA: { + if (cellIdentity.cellIdentityTdscdma.size() == 1) { + return new CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0)); + } + break; + } + case CellInfoType.LTE: { + if (cellIdentity.cellIdentityLte.size() == 1) { + return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0)); + } + break; + } + case CellInfoType.CDMA: { + if (cellIdentity.cellIdentityCdma.size() == 1) { + return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0)); + } + break; + } + case CellInfoType.NONE: break; + default: break; + } + return null; + } + + /** @hide */ public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) { if (ci == null) return null; switch (ci.getDiscriminator()) { diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java index 11931996009c..2d0bd52f84ee 100644 --- a/telephony/java/android/telephony/CellLocation.java +++ b/telephony/java/android/telephony/CellLocation.java @@ -53,8 +53,7 @@ public abstract class CellLocation { /** * Create a new CellLocation from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method maybe used by external applications. * * @param bundle Bundle from intent notifier * @return newly created CellLocation diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java index 39af34c0e0f0..c706d288b7f2 100644 --- a/telephony/java/android/telephony/ImsManager.java +++ b/telephony/java/android/telephony/ImsManager.java @@ -17,6 +17,8 @@ package android.telephony.ims; import android.annotation.NonNull; +import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -35,7 +37,26 @@ public class ImsManager { private Context mContext; - /** @hide */ + /** + * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it + * not being authorized on the network. + * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify + * which subscription the operation was rejected for. + * <p class="note"> + * Carrier applications may listen to this broadcast to be notified of possible IMS provisioning + * issues. + */ + // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have + // this value hard-coded in BroadcastReceiver. + @SuppressLint("ActionValue") + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = + "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; + + /** + * Use {@link Context#getSystemService(String)} to get an instance of this class. + * @hide + */ public ImsManager(@NonNull Context context) { mContext = context; } diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index 31434c1d2adf..5e2e554b7764 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -20,6 +20,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.net.LinkProperties; import android.os.Build; @@ -31,8 +34,6 @@ import android.telephony.Annotation.DataState; import android.telephony.Annotation.NetworkType; import android.telephony.data.ApnSetting; -import dalvik.system.VMRuntime; - import java.util.Objects; @@ -134,6 +135,13 @@ public final class PreciseDataConnectionState implements Parcelable { } /** + * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L; + + /** * Returns the state of data connection that supported the apn types returned by * {@link #getDataConnectionApnTypeBitMask()} * @@ -144,7 +152,7 @@ public final class PreciseDataConnectionState implements Parcelable { @SystemApi public @DataState int getDataConnectionState() { if (mState == TelephonyManager.DATA_DISCONNECTING - && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) { + && !Compatibility.isChangeEnabled(GET_DATA_CONNECTION_STATE_CODE_CHANGE)) { return TelephonyManager.DATA_CONNECTED; } diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index 1f7d55f2758a..1c58f8faf7cf 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -87,8 +87,7 @@ public class SignalStrength implements Parcelable { /** * Create a new SignalStrength from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method may be used by external applications. * * @param m Bundle from intent notifier * @return newly created SignalStrength diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index db33be313941..8ed4ee594d73 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -281,6 +281,42 @@ public final class SmsManager { */ public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1; + /** @hide */ + @IntDef(prefix = { "PREMIUM_SMS_CONSENT" }, value = { + SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN, + SmsManager.PREMIUM_SMS_CONSENT_ASK_USER, + SmsManager.PREMIUM_SMS_CONSENT_NEVER_ALLOW, + SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PremiumSmsConsent {} + + /** Premium SMS Consent for the package is unknown. This indicates that the user + * has not set a permission for this package, because this package has never tried + * to send a premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; + + /** Default premium SMS Consent (ask user for each premium SMS sent). + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; + + /** Premium SMS Consent when the owner has denied the app from sending premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; + + /** Premium SMS Consent when the owner has allowed the app to send premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; + // result of asking the user for a subscription to perform an operation. private interface SubscriptionResolverResult { void onSuccess(int subId); @@ -2539,7 +2575,7 @@ public final class SmsManager { * </p> * * @return the bundle key/values pairs that contains MMS configuration values - * or an empty bundle if they cannot be found. + * or an empty Bundle if they cannot be found. */ @NonNull public Bundle getCarrierConfigValues() { try { @@ -2869,4 +2905,53 @@ public final class SmsManager { } return false; } + + /** + * Gets the premium SMS permission for the specified package. If the package has never + * been seen before, the default {@link SmsManager#PREMIUM_SMS_PERMISSION_ASK_USER} + * will be returned. + * @param packageName the name of the package to query permission + * @return one of {@link SmsManager#PREMIUM_SMS_CONSENT_UNKNOWN}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or + * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW} + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @PremiumSmsConsent int getPremiumSmsConsent(@NonNull String packageName) { + int permission = 0; + try { + ISms iSms = getISmsService(); + if (iSms != null) { + permission = iSms.getPremiumSmsPermission(packageName); + } + } catch (RemoteException e) { + Log.e(TAG, "getPremiumSmsPermission() RemoteException", e); + } + return permission; + } + + /** + * Sets the premium SMS permission for the specified package and save the value asynchronously + * to persistent storage. + * @param packageName the name of the package to set permission + * @param permission one of {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or + * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW} + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setPremiumSmsConsent( + @NonNull String packageName, @PremiumSmsConsent int permission) { + try { + ISms iSms = getISmsService(); + if (iSms != null) { + iSms.setPremiumSmsPermission(packageName, permission); + } + } catch (RemoteException e) { + Log.e(TAG, "setPremiumSmsPermission() RemoteException", e); + } + } } diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index c217b8b83c26..40a7619a3ee3 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -20,8 +20,13 @@ import com.android.telephony.Rlog; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.StringDef; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Binder; @@ -31,8 +36,10 @@ import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; import com.android.internal.telephony.Sms7BitEncodingTranslator; import com.android.internal.telephony.SmsConstants; +import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; +import com.android.internal.telephony.cdma.sms.UserData; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -56,6 +63,16 @@ public class SmsMessage { UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3; } + /** @hide */ + @IntDef(prefix = { "ENCODING_" }, value = { + ENCODING_UNKNOWN, + ENCODING_7BIT, + ENCODING_8BIT, + ENCODING_16BIT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EncodingSize {} + /** User data text encoding code unit size */ public static final int ENCODING_UNKNOWN = 0; public static final int ENCODING_7BIT = 1; @@ -633,6 +650,83 @@ public class SmsMessage { } /** + * Get an SMS-SUBMIT PDU's encoded message. + * This is used by Bluetooth MAP profile to handle long non UTF-8 SMS messages. + * + * @param isTypeGsm true when message's type is GSM, false when type is CDMA + * @param destinationAddress the address of the destination for the message + * @param message message content + * @param encoding User data text encoding code unit size + * @param languageTable GSM national language table to use, specified by 3GPP + * 23.040 9.2.3.24.16 + * @param languageShiftTable GSM national language shift table to use, specified by 3GPP + * 23.040 9.2.3.24.15 + * @param refNumber parameter to create SmsHeader + * @param seqNumber parameter to create SmsHeader + * @param msgCount parameter to create SmsHeader + * @return a byte[] containing the encoded message + * + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @SystemApi + @NonNull + public static byte[] getSubmitPduEncodedMessage(boolean isTypeGsm, + @NonNull String destinationAddress, + @NonNull String message, + @EncodingSize int encoding, int languageTable, + int languageShiftTable, int refNumber, + int seqNumber, int msgCount) { + byte[] data; + SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); + concatRef.refNumber = refNumber; + concatRef.seqNumber = seqNumber; // 1-based sequence + concatRef.msgCount = msgCount; + // We currently set this to true since our messaging app will never + // send more than 255 parts (it converts the message to MMS well before that). + // However, we should support 3rd party messaging apps that might need 16-bit + // references + // Note: It's not sufficient to just flip this bit to true; it will have + // ripple effects (several calculations assume 8-bit ref). + concatRef.isEightBits = true; + SmsHeader smsHeader = new SmsHeader(); + smsHeader.concatRef = concatRef; + + /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding + * will be determined(again) by getSubmitPdu(). + * All packets need to be encoded using the same encoding, as the bMessage + * only have one filed to describe the encoding for all messages in a concatenated + * SMS... */ + if (encoding == ENCODING_7BIT) { + smsHeader.languageTable = languageTable; + smsHeader.languageShiftTable = languageShiftTable; + } + + if (isTypeGsm) { + data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, + destinationAddress, message, false, + SmsHeader.toByteArray(smsHeader), encoding, languageTable, + languageShiftTable).encodedMessage; + } else { // SMS_TYPE_CDMA + UserData uData = new UserData(); + uData.payloadStr = message; + uData.userDataHeader = smsHeader; + if (encoding == ENCODING_7BIT) { + uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; + } else { // assume UTF-16 + uData.msgEncoding = UserData.ENCODING_UNICODE_16; + } + uData.msgEncodingSet = true; + data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( + destinationAddress, uData, false).encodedMessage; + } + if (data == null) { + return new byte[0]; + } + return data; + } + + /** * Returns the address of the SMS service center that relayed this message * or null if there is none. */ diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4510fede4e8e..36f541f0a81d 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -16,8 +16,6 @@ package android.telephony; -import com.android.telephony.Rlog; - import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; @@ -67,6 +65,7 @@ import com.android.internal.telephony.ISub; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.util.HandlerExecutor; import com.android.internal.util.Preconditions; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -2240,10 +2239,9 @@ public class SubscriptionManager { @UnsupportedAppUsage public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) { if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId); - intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); - intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); intent.putExtra(EXTRA_SLOT_INDEX, phoneId); intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); + putSubscriptionIdExtra(intent, subId); } /** @@ -3489,4 +3487,19 @@ public class SubscriptionManager { } return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } + + /** + * Helper method that puts a subscription id on an intent with the constants: + * PhoneConstant.SUBSCRIPTION_KEY and SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX. + * Both constants are used to support backwards compatibility. Once we know we got all places, + * we can remove PhoneConstants.SUBSCRIPTION_KEY. + * @param intent Intent to put sub id on. + * @param subId SubscriptionId to put on intent. + * + * @hide + */ + public static void putSubscriptionIdExtra(Intent intent, int subId) { + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); + intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 843c0656efc3..0ffeccfa6a23 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -46,6 +46,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkStats; @@ -108,8 +109,6 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.SmsApplication; import com.android.telephony.Rlog; -import dalvik.system.VMRuntime; - import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.Retention; @@ -449,12 +448,8 @@ public class TelephonyManager { case UNKNOWN: modemCount = MODEM_COUNT_SINGLE_MODEM; // check for voice and data support, 0 if not supported - if (!isVoiceCapable() && !isSmsCapable() && mContext != null) { - ConnectivityManager cm = (ConnectivityManager) mContext - .getSystemService(Context.CONNECTIVITY_SERVICE); - if (cm != null && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { - modemCount = MODEM_COUNT_NO_MODEM; - } + if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) { + modemCount = MODEM_COUNT_NO_MODEM; } break; case DSDS: @@ -1447,48 +1442,231 @@ public class TelephonyManager { "android.telephony.extra.SIM_COMBINATION_NAMES"; /** - * Broadcast Action: The time was set by the carrier (typically by the NITZ string). - * This is a sticky broadcast. - * The intent will have the following extra values:</p> + * <p>Broadcast Action: The emergency callback mode is changed. * <ul> - * <li><em>time</em> - The time as a long in UTC milliseconds.</li> + * <li><em>EXTRA_PHONE_IN_ECM_STATE</em> - A boolean value,true=phone in ECM, + * false=ECM off</li> * </ul> - * * <p class="note"> - * Requires the READ_PHONE_STATE permission. + * You can <em>not</em> receive this through components declared + * in manifests, only by explicitly registering for it with + * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, + * android.content.IntentFilter) Context.registerReceiver()}. * * <p class="note">This is a protected intent that can only be sent by the system. * + * @see #EXTRA_PHONE_IN_ECM_STATE + * * @hide */ @SystemApi - public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME"; + @SuppressLint("ActionValue") + public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = + "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; + /** - * <p>Broadcast Action: The emergency callback mode is changed. + * Extra included in {@link #ACTION_EMERGENCY_CALLBACK_MODE_CHANGED}. + * Indicates whether the phone is in an emergency phone state. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PHONE_IN_ECM_STATE = + "android.telephony.extra.PHONE_IN_ECM_STATE"; + + /** + * <p>Broadcast Action: when data connections get redirected with validation failure. + * intended for sim/account status checks and only sent to the specified carrier app + * The intent will have the following extra values:</p> * <ul> - * <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li> + * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd> + * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd> + * <li>{@link #EXTRA_REDIRECTION_URL}</li><dd>redirection url string</dd> + * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> * </ul> - * <p class="note"> - * You can <em>not</em> receive this through components declared - * in manifests, only by explicitly registering for it with - * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, - * android.content.IntentFilter) Context.registerReceiver()}. + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = + "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"; + + /** + * <p>Broadcast Action: when data connections setup fails. + * intended for sim/account status checks and only sent to the specified carrier app + * The intent will have the following extra values:</p> + * <ul> + * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd> + * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd> + * <li>{@link #EXTRA_ERROR_CODE}</li><dd>A integer with dataFailCause.</dd> + * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system. </p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = + "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED"; + + /** + * <p>Broadcast Action: when pco value is available. + * intended for sim/account status checks and only sent to the specified carrier app + * The intent will have the following extra values:</p> + * <ul> + * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd> + * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd> + * <li>{@link #EXTRA_APN_PROTOCOL}</li><dd>A string with the protocol of the apn connection + * (IP,IPV6, IPV4V6)</dd> + * <li>{@link #EXTRA_APN_PROTOCOL_INT}</li><dd>A integer with the protocol of the apn + * connection (IP,IPV6, IPV4V6)</dd> + * <li>{@link #EXTRA_PCO_ID}</li><dd>An integer indicating the pco id for the data.</dd> + * <li>{@link #EXTRA_PCO_VALUE}</li><dd>A byte array of pco data read from modem.</dd> + * <li>subId</li><dd>Sub Id which associated the data connection.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system. </p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = + "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE"; + + /** + * <p>Broadcast Action: when system default network available/unavailable with + * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when + * other network becomes system default network, Wi-Fi for example. + * The intent will have the following extra values:</p> + * <ul> + * <li>{@link #EXTRA_DEFAULT_NETWORK_AVAILABLE}</li> + * <dd>A boolean indicates default network available.</dd> + * <li>subId</li><dd>Sub Id which associated the default data.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system. </p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = + "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"; + + /** + * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent. + * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app + * The intent will have the following extra values:</p> + * <ul> + * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_RESET = + "com.android.internal.telephony.CARRIER_SIGNAL_RESET"; + + // CARRIER_SIGNAL_ACTION extra keys + /** + * An string extra of redirected url upon {@link #ACTION_CARRIER_SIGNAL_REDIRECTED}. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_REDIRECTION_URL = "redirectionUrl"; + + /** + * An integer extra of error code upon {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}. + * Check {@link DataFailCause} for all possible values. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_ERROR_CODE = "errorCode"; + + /** + * An string extra of corresponding apn type upon + * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}, + * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @deprecated This is kept for backward compatibility reason. Use {@link #EXTRA_APN_TYPE_INT} + * instead. * - * <p class="note">This is a protected intent that can only be sent by the system. + * @hide + */ + @SystemApi + @Deprecated + @SuppressLint("ActionValue") + public static final String EXTRA_APN_TYPE = "apnType"; + + /** + * An string integer of corresponding apn type upon + * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}, + * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * Check {@link ApnSetting} TYPE_* for its values. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_APN_TYPE_INT = "apnTypeInt"; + + /** + * An string extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @deprecated This is kept for backward compatibility reason. + * Use {@link #EXTRA_APN_PROTOCOL_INT} instead. * - * @hide + * @hide */ @SystemApi + @Deprecated @SuppressLint("ActionValue") - public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED - = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; + public static final String EXTRA_APN_PROTOCOL = "apnProto"; + + /** + * An integer extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * Check {@link ApnSetting} PROTOCOL_* for its values. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt"; + + /** + * An integer extra indicating the pco id for the data upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_PCO_ID = "pcoId"; + + /** + * An extra of byte array of pco data read from modem upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_PCO_VALUE = "pcoValue"; + + /** + * An boolean extra indicating default network available upon + * {@link #ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE} broadcasts. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable"; /** * <p>Broadcast Action: The emergency call state is changed. * <ul> - * <li><em>phoneInEmergencyCall</em> - A boolean value, true if phone in emergency call, - * false otherwise</li> + * <li><em>EXTRA_PHONE_IN_EMERGENCY_CALL</em> - A boolean value, true if phone in emergency + * call, false otherwise</li> * </ul> * <p class="note"> * You can <em>not</em> receive this through components declared @@ -1498,12 +1676,25 @@ public class TelephonyManager { * * <p class="note">This is a protected intent that can only be sent by the system. * + * @see #EXTRA_PHONE_IN_EMERGENCY_CALL + * * @hide */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED - = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED"; + public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = + "android.intent.action.EMERGENCY_CALL_STATE_CHANGED"; + + + /** + * Extra included in {@link #ACTION_EMERGENCY_CALL_STATE_CHANGED}. + * It indicates whether the phone is making an emergency call. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = + "android.telephony.extra.PHONE_IN_EMERGENCY_CALL"; /** * <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms @@ -1516,8 +1707,8 @@ public class TelephonyManager { * @hide */ @SystemApi - public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS - = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS"; + public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = + "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS"; /** * Broadcast Action: The default data subscription has changed in a multi-SIM device. @@ -1530,8 +1721,8 @@ public class TelephonyManager { */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED - = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"; + public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED = + "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"; /** * Broadcast Action: The default voice subscription has changed in a mult-SIm device. @@ -1544,8 +1735,8 @@ public class TelephonyManager { */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED - = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED"; + public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED = + "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED"; /** * Broadcast Action: This triggers a client initiated OMA-DM session to the OMA server. @@ -1558,8 +1749,8 @@ public class TelephonyManager { */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE - = "com.android.omadm.service.CONFIGURATION_UPDATE"; + public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = + "com.android.omadm.service.CONFIGURATION_UPDATE"; // // @@ -5061,7 +5252,9 @@ public class TelephonyManager { * not present or not loaded * @hide */ - @UnsupportedAppUsage + @Nullable + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu() { try { IPhoneSubInfo info = getSubscriberInfo(); @@ -5243,6 +5436,13 @@ public class TelephonyManager { public static final int DATA_DISCONNECTING = 4; /** + * To check the SDK version for {@link TelephonyManager#getDataState}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L; + + /** * Returns a constant indicating the current data connection state * (cellular). * @@ -5260,7 +5460,7 @@ public class TelephonyManager { int state = telephony.getDataStateForSubId( getSubId(SubscriptionManager.getActiveDataSubscriptionId())); if (state == TelephonyManager.DATA_DISCONNECTING - && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) { + && !Compatibility.isChangeEnabled(GET_DATA_STATE_CODE_CHANGE)) { return TelephonyManager.DATA_CONNECTED; } @@ -5322,6 +5522,13 @@ public class TelephonyManager { // /** + * To check the SDK version for {@link TelephonyManager#listen}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long LISTEN_CODE_CHANGE = 147600208L; + + /** * Registers a listener object to receive notification of changes * in specified telephony states. * <p> @@ -5360,7 +5567,7 @@ public class TelephonyManager { // subId from PhoneStateListener is deprecated Q on forward, use the subId from // TelephonyManager instance. keep using subId from PhoneStateListener for pre-Q. int subId = mSubId; - if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.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. // TODO: remove this once we completely get rid of mSubId in PhoneStateListener @@ -10669,12 +10876,21 @@ public class TelephonyManager { } /** + * Checks whether cellular data connection is enabled in the device. + * + * Whether cellular data connection is enabled, meaning upon request whether will try to setup + * metered data connection considering all factors below: + * 1) User turned on data setting {@link #isDataEnabled}. + * 2) Carrier allows data to be on. + * 3) Network policy. + * And possibly others. + * + * @return {@code true} if the overall data connection is capable; {@code false} if not. * @hide - * It's similar to isDataEnabled, but unlike isDataEnabled, this API also evaluates - * carrierDataEnabled, policyDataEnabled etc to give a final decision of whether mobile data is - * capable of using. */ - public boolean isDataCapable() { + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isDataConnectionEnabled() { boolean retVal = false; try { int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId()); @@ -10682,13 +10898,24 @@ public class TelephonyManager { if (telephony != null) retVal = telephony.isDataEnabled(subId); } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#isDataEnabled", e); + Log.e(TAG, "Error isDataConnectionEnabled", e); } catch (NullPointerException e) { } return retVal; } /** + * Checks if FEATURE_TELEPHONY_DATA is enabled. + * + * @hide + */ + public boolean isDataCapable() { + if (mContext == null) return true; + return mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_DATA); + } + + /** * In this mode, modem will not send specified indications when screen is off. * @hide */ diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java index 121401277ce9..d4a27d925208 100644 --- a/telephony/java/android/telephony/VoLteServiceState.java +++ b/telephony/java/android/telephony/VoLteServiceState.java @@ -53,8 +53,7 @@ public final class VoLteServiceState implements Parcelable { /** * Create a new VoLteServiceState from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method is maybe used by external applications. * * @param m Bundle from intent notifier * @return newly created VoLteServiceState diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index c96271432ea2..d4832917908e 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.telephony.AccessNetworkConstants; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsRcsController; diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index f0521802a167..6005f77605a6 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -34,6 +34,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsConfigCallback; import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.feature.RcsFeature; import android.telephony.ims.stub.ImsConfigImplBase; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -84,6 +85,11 @@ public class ProvisioningManager { "STRING_QUERY_RESULT_ERROR_NOT_READY"; /** + * There is no existing configuration for the queried provisioning key. + */ + public static final int PROVISIONING_RESULT_UNKNOWN = -1; + + /** * The integer result of provisioning for the queried key is disabled. */ public static final int PROVISIONING_VALUE_DISABLED = 0; @@ -94,6 +100,151 @@ public class ProvisioningManager { public static final int PROVISIONING_VALUE_ENABLED = 1; + // Inheriting values from ImsConfig for backwards compatibility. + /** + * An integer key representing the SIP T1 timer value in milliseconds for the associated + * subscription. + * <p> + * The SIP T1 timer is an estimate of the round-trip time and will retransmit + * INVITE transactions that are longer than T1 milliseconds over unreliable transports, doubling + * the time before retransmission every time there is no response. See RFC3261, section 17.1.1.1 + * for more details. + * <p> + * The value is an integer. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_T1_TIMER_VALUE_MS = 7; + + /** + * An integer key representing the voice over LTE (VoLTE) provisioning status for the + * associated subscription. Determines whether the user can register for voice services over + * LTE. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoLTE provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; + + /** + * An integer key representing the video telephony (VT) provisioning status for the + * associated subscription. Determines whether the user can register for video services over + * LTE. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VT provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_VT_PROVISIONING_STATUS = 11; + + /** + * An integer key associated with the carrier configured SIP PUBLISH timer, which dictates the + * expiration time in seconds for published online availability in RCS presence. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; + + /** + * An integer key associated with the carrier configured expiration time in seconds for + * RCS presence published offline availability in RCS presence. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; + + /** + * An integer key associated with whether or not capability discovery is provisioned for this + * subscription. Any capability requests will be ignored by the RCS service. + * <p> + * The value is an integer, either {@link #PROVISIONING_VALUE_DISABLED} if capability + * discovery is disabled or {@link #PROVISIONING_VALUE_ENABLED} if capability discovery is + * enabled. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; + + /** + * An integer key associated with the period of time the capability information of each contact + * is cached on the device. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; + + /** + * An integer key associated with the period of time in seconds that the availability + * information of a contact is cached on the device. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; + + /** + * An integer key associated with the carrier configured interval in seconds expected between + * successive capability polling attempts. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; + + /** + * An integer key representing the minimum time allowed between two consecutive presence publish + * messages from the device. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; + + /** + * An integer key associated with the maximum number of MDNs contained in one SIP Request + * Contained List (RCS) used to retrieve the RCS capabilities of the contacts book. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; + + /** + * An integer associated with the expiration timer used duriing the SIP subscription of a + * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact + * book. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; + + /** + * An integer key representing the RCS enhanced address book (EAB) provisioning status for the + * associated subscription. Determines whether or not SIP OPTIONS or presence will be used to + * retrieve RCS capabilities for the user's contacts. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable EAB provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_EAB_PROVISIONING_STATUS = 25; + /** * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning @@ -269,7 +420,7 @@ public class ProvisioningManager { * * @param key An integer that represents the provisioning key, which is defined by the OEM. * @return an integer value for the provided key, or - * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist. + * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist. * @throws IllegalArgumentException if the key provided was invalid. */ @WorkerThread @@ -396,6 +547,54 @@ public class ProvisioningManager { } /** + * Get the provisioning status for the IMS RCS capability specified. + * + * If provisioning is not required for the queried + * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return + * {@code true}. + * + * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean getRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) { + try { + return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Set the provisioning status for the IMS RCS capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @param isProvisioned true if the device is provisioned for the RCS capability specified, + * false otherwise. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + boolean isProvisioned) { + try { + getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability, + isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Notify the framework that an RCS autoconfiguration XML file has been received for * provisioning. * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed. diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java index 492170b1069a..893a311e646b 100644 --- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java +++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java @@ -19,6 +19,7 @@ package android.telephony.ims; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +36,7 @@ import java.util.Map; * Contains the User Capability Exchange capabilities corresponding to a contact's URI. * @hide */ +@SystemApi public final class RcsContactUceCapability implements Parcelable { /** Supports 1-to-1 chat */ @@ -135,7 +137,7 @@ public final class RcsContactUceCapability implements Parcelable { * @param type The capability to map to a service URI that is different from the contact's * URI. */ - public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) { + public @NonNull Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) { mCapabilities.mCapabilities |= type; // Put each of these capabilities into the map separately. for (int shift = 0; shift < Integer.SIZE; shift++) { @@ -157,7 +159,7 @@ public final class RcsContactUceCapability implements Parcelable { * Add a UCE capability flag that this contact supports. * @param type the capability that the contact supports. */ - public Builder add(@CapabilityFlag int type) { + public @NonNull Builder add(@CapabilityFlag int type) { mCapabilities.mCapabilities |= type; return this; } @@ -167,7 +169,7 @@ public final class RcsContactUceCapability implements Parcelable { * @param extension A string containing a carrier specific service tag that is an extension * of the {@link CapabilityFlag}s that are defined here. */ - public Builder add(@NonNull String extension) { + public @NonNull Builder add(@NonNull String extension) { mCapabilities.mExtensionTags.add(extension); return this; } @@ -175,7 +177,7 @@ public final class RcsContactUceCapability implements Parcelable { /** * @return the constructed instance. */ - public RcsContactUceCapability build() { + public @NonNull RcsContactUceCapability build() { return mCapabilities; } } @@ -205,7 +207,7 @@ public final class RcsContactUceCapability implements Parcelable { } } - public static final Creator<RcsContactUceCapability> CREATOR = + public static final @NonNull Creator<RcsContactUceCapability> CREATOR = new Creator<RcsContactUceCapability>() { @Override public RcsContactUceCapability createFromParcel(Parcel in) { @@ -219,7 +221,7 @@ public final class RcsContactUceCapability implements Parcelable { }; @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mContactUri, 0); out.writeInt(mCapabilities); out.writeStringList(mExtensionTags); diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index d18e93c7e8bf..60cf216627a3 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -22,6 +22,7 @@ import android.annotation.TestApi; import android.content.Context; import android.os.PersistableBundle; import android.os.RemoteException; +import android.telephony.ims.ProvisioningManager; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; import android.util.Log; @@ -228,7 +229,8 @@ public class ImsConfigImplBase { * The configuration requested resulted in an unknown result. This may happen if the * IMS configurations are unavailable. */ - public static final int CONFIG_RESULT_UNKNOWN = -1; + public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN; + /** * Setting the configuration value completed. */ diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index 9116a3bf3bde..0d86e2b7c2b1 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -178,7 +178,7 @@ public class ImsConfig { * SIP T1 timer value in milliseconds. See RFC 3261 for define. * Value is in Integer format. */ - public static final int SIP_T1_TIMER = 7; + public static final int SIP_T1_TIMER = ProvisioningManager.KEY_T1_TIMER_VALUE_MS; /** * SIP T2 timer value in milliseconds. See RFC 3261 for define. @@ -196,13 +196,15 @@ public class ImsConfig { * VoLTE status for VLT/s status of Enabled (1), or Disabled (0). * Value is in Integer format. */ - public static final int VLT_SETTING_ENABLED = 10; + public static final int VLT_SETTING_ENABLED = + ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS; /** * VoLTE status for LVC/s status of Enabled (1), or Disabled (0). * Value is in Integer format. */ - public static final int LVC_SETTING_ENABLED = 11; + public static final int LVC_SETTING_ENABLED = + ProvisioningManager.KEY_VT_PROVISIONING_STATUS; /** * Domain Name for the device to populate the request URI for REGISTRATION. * Value is in String format. @@ -222,48 +224,56 @@ public class ImsConfig { * Requested expiration for Published Online availability. * Value is in Integer format. */ - public static final int PUBLISH_TIMER = 15; + public static final int PUBLISH_TIMER = ProvisioningManager.KEY_RCS_PUBLISH_TIMER_SEC; /** * Requested expiration for Published Offline availability. * Value is in Integer format. */ - public static final int PUBLISH_TIMER_EXTENDED = 16; + public static final int PUBLISH_TIMER_EXTENDED = + ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC; /** * * Value is in Integer format. */ - public static final int CAPABILITY_DISCOVERY_ENABLED = 17; + public static final int CAPABILITY_DISCOVERY_ENABLED = + ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED; /** * Period of time the capability information of the contact is cached on handset. * Value is in Integer format. */ - public static final int CAPABILITIES_CACHE_EXPIRATION = 18; + public static final int CAPABILITIES_CACHE_EXPIRATION = + ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC; /** * Peiod of time the availability information of a contact is cached on device. * Value is in Integer format. */ - public static final int AVAILABILITY_CACHE_EXPIRATION = 19; + public static final int AVAILABILITY_CACHE_EXPIRATION = + ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC; /** * Interval between successive capabilities polling. * Value is in Integer format. */ - public static final int CAPABILITIES_POLL_INTERVAL = 20; + public static final int CAPABILITIES_POLL_INTERVAL = + ProvisioningManager.KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC; /** * Minimum time between two published messages from the device. * Value is in Integer format. */ - public static final int SOURCE_THROTTLE_PUBLISH = 21; + public static final int SOURCE_THROTTLE_PUBLISH = + ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS; /** * The Maximum number of MDNs contained in one Request Contained List. * Value is in Integer format. */ - public static final int MAX_NUMENTRIES_IN_RCL = 22; + public static final int MAX_NUMENTRIES_IN_RCL = + ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL; /** * Expiration timer for subscription of a Request Contained List, used in capability * polling. * Value is in Integer format. */ - public static final int CAPAB_POLL_LIST_SUB_EXP = 23; + public static final int CAPAB_POLL_LIST_SUB_EXP = + ProvisioningManager.KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC; /** * Applies compression to LIST Subscription. * Value is in Integer format. Enable (1), Disable(0). @@ -273,7 +283,8 @@ public class ImsConfig { * VOLTE Status for EAB/s status of Enabled (1), or Disabled (0). * Value is in Integer format. */ - public static final int EAB_SETTING_ENABLED = 25; + public static final int EAB_SETTING_ENABLED = + ProvisioningManager.KEY_EAB_PROVISIONING_STATUS; /** * Wi-Fi calling roaming status. * Value is in Integer format. ON (1), OFF(0). diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index b846a1029c68..28f3974162b7 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1977,6 +1977,17 @@ interface ITelephony { */ boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech); + /** + * Get the provisioning status for the IMS Rcs capability specified. + */ + boolean getRcsProvisioningStatusForCapability(int subId, int capability); + + /** + * Set the provisioning status for the IMS Rcs capability using the specified subscription. + */ + void setRcsProvisioningStatusForCapability(int subId, int capability, + boolean isProvisioned); + /** Is the capability and tech flagged as provisioned in the cache */ boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java index 51701eb4ace5..db8c84560282 100644 --- a/telephony/java/com/android/internal/telephony/PhoneConstants.java +++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java @@ -100,9 +100,6 @@ public class PhoneConstants { public static final String DATA_APN_TYPE_KEY = "apnType"; public static final String DATA_APN_KEY = "apn"; - public static final String PHONE_IN_ECM_STATE = "phoneinECMState"; - public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall"; - /** * Return codes for supplyPinReturnResult and * supplyPukReturnResult APIs diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java index f78c65ffb3aa..a15f73cf348d 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java +++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java @@ -19,6 +19,7 @@ package com.android.internal.telephony; import android.content.Intent; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.ims.ImsManager; /** * The intents that the telephony services broadcast. @@ -123,32 +124,6 @@ public class TelephonyIntents { public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED; - /** - * Broadcast Action: The phone's signal strength has changed. The intent will have the - * following extra values:</p> - * <ul> - * <li><em>phoneName</em> - A string version of the phone name.</li> - * <li><em>asu</em> - A numeric value for the signal strength. - * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu). - * The following special values are defined: - * <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul> - * </li> - * </ul> - * - * <p class="note"> - * You can <em>not</em> receive this through components declared - * in manifests, only by exlicitly registering for it with - * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, - * android.content.IntentFilter) Context.registerReceiver()}. - * - * <p class="note"> - * Requires the READ_PHONE_STATE permission. - * - * <p class="note">This is a protected intent that can only be sent - * by the system. - */ - public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR"; - /** * Broadcast Action: The data connection state has changed for any one of the @@ -224,9 +199,11 @@ public class TelephonyIntents { * <p class="note"> * This is for the OEM applications to understand about possible provisioning issues. * Used in OMA-DM applications. + * @deprecated Use {@link ImsManager#ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION} instead. */ - public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION - = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; + @Deprecated + public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = + ImsManager.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION; /** * Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are @@ -351,85 +328,6 @@ public class TelephonyIntents { "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED"; /** - * <p>Broadcast Action: when data connections get redirected with validation failure. - * intended for sim/account status checks and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>apnType</li><dd>A string with the apn type.</dd> - * <li>redirectionUrl</li><dd>redirection url string</dd> - * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system.</p> - */ - public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = - "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"; - /** - * <p>Broadcast Action: when data connections setup fails. - * intended for sim/account status checks and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>apnType</li><dd>A string with the apn type.</dd> - * <li>errorCode</li><dd>A integer with dataFailCause.</dd> - * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system. </p> - */ - public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = - "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED"; - - /** - * <p>Broadcast Action: when pco value is available. - * intended for sim/account status checks and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>apnType</li><dd>A string with the apn type.</dd> - * <li>apnProto</li><dd>A string with the protocol of the apn connection (IP,IPV6, - * IPV4V6)</dd> - * <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd> - * <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd> - * <li>subId</li><dd>Sub Id which associated the data connection.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system. </p> - */ - public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = - "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE"; - - /** - * <p>Broadcast Action: when system default network available/unavailable with - * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when - * other network becomes system default network, Wi-Fi for example. - * The intent will have the following extra values:</p> - * <ul> - * <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd> - * <li>subId</li><dd>Sub Id which associated the default data.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system. </p> - */ - public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = - "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"; - - /** - * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent. - * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system.</p> - */ - public static final String ACTION_CARRIER_SIGNAL_RESET = - "com.android.internal.telephony.CARRIER_SIGNAL_RESET"; - - // CARRIER_SIGNAL_ACTION extra keys - public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl"; - public static final String EXTRA_ERROR_CODE_KEY = "errorCode"; - public static final String EXTRA_APN_TYPE_KEY = "apnType"; - public static final String EXTRA_APN_PROTO_KEY = "apnProto"; - public static final String EXTRA_PCO_ID_KEY = "pcoId"; - public static final String EXTRA_PCO_VALUE_KEY = "pcoValue"; - public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable"; - - /** * Broadcast action to trigger CI OMA-DM Session. */ public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 2bc129ae4840..091edd4dc0d9 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -19,15 +19,17 @@ android_test { static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"], test_suites: ["general-tests"], test_config: "RollbackTest.xml", + java_resources: [":com.android.apex.apkrollback.test_v2"], } java_test_host { name: "StagedRollbackTest", srcs: ["StagedRollbackTest/src/**/*.java"], libs: ["tradefed"], - static_libs: ["testng"], + static_libs: ["testng", "compatibility-tradefed"], test_suites: ["general-tests"], test_config: "StagedRollbackTest.xml", + data: [":com.android.apex.apkrollback.test_v1"], } java_test_host { @@ -37,3 +39,44 @@ java_test_host { test_suites: ["general-tests"], test_config: "MultiUserRollbackTest.xml", } + +genrule { + name: "com.android.apex.apkrollback.test.pem", + out: ["com.android.apex.apkrollback.test.pem"], + cmd: "openssl genrsa -out $(out) 4096", +} + +genrule { + name: "com.android.apex.apkrollback.test.pubkey", + srcs: [":com.android.apex.apkrollback.test.pem"], + out: ["com.android.apex.apkrollback.test.pubkey"], + tools: ["avbtool"], + cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)", +} + +apex_key { + name: "com.android.apex.apkrollback.test.key", + private_key: ":com.android.apex.apkrollback.test.pem", + public_key: ":com.android.apex.apkrollback.test.pubkey", + installable: false, +} + +apex { + name: "com.android.apex.apkrollback.test_v1", + manifest: "testdata/manifest_v1.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv1"], + installable: false, +} + +apex { + name: "com.android.apex.apkrollback.test_v2", + manifest: "testdata/manifest_v2.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv2"], + installable: false, +}
\ No newline at end of file diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 9e490f765eab..3877cc139a3e 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -435,11 +435,64 @@ public class StagedRollbackTest { // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks // so there is only one rollback to commit when testing native crashes. - RollbackManager rm = RollbackUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); } + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1", + APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); + private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", + APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); + private static final TestApp TEST_APP_A_V2_UNKNOWN = new TestApp("Av2Unknown", TestApp.A, 0, + /*isApex*/false, "TestAppAv2.apk"); + + @Test + public void testRollbackApexWithApk_Phase1() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + + int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() + .commit(); + InstallUtils.waitForSessionReady(sessionId); + } + + @Test + public void testRollbackApexWithApk_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.processUserData(TestApp.A); + + RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); + assertThat(available).isStaged(); + assertThat(available).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + + RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); + RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); + assertThat(committed).isNotNull(); + assertThat(committed).isStaged(); + assertThat(committed).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2); + assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1); + + // Note: The app is not rolled back until after the rollback is staged + // and the device has been rebooted. + InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + } + + @Test + public void testRollbackApexWithApk_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + } + private static void runShellCommand(String cmd) { ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() .executeShellCommand(cmd); diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 91577c202df9..6daa6bc723c4 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -19,6 +19,7 @@ package com.android.tests.rollback.host; import static org.junit.Assert.assertTrue; import static org.testng.Assert.assertThrows; +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -27,6 +28,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.util.concurrent.TimeUnit; /** @@ -48,14 +50,32 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { phase)); } + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + @Before public void setUp() throws Exception { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + getDevice().executeShellCommand( + "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex " + + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); getDevice().reboot(); } @After public void tearDown() throws Exception { runPhase("testCleanUp"); + + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + getDevice().executeShellCommand( + "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex " + + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); + getDevice().reboot(); } /** @@ -184,6 +204,28 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testRollbackDataPolicy_Phase3"); } + /** + * Tests that userdata of apk-in-apex is restored when apex is rolled back. + */ + @Test + public void testRollbackApexWithApk() throws Exception { + getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); + final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; + final File apex = buildHelper.getTestFile(fileName); + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase1"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase2"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase3"); + } + private void crashProcess(String processName, int numberOfCrashes) throws Exception { String pid = ""; String lastPid = "invalid"; diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml new file mode 100644 index 000000000000..f21ec899eb69 --- /dev/null +++ b/tests/RollbackTest/testdata/AndroidManifest.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.apex.apkrollback.test"> + <!-- APEX does not have classes.dex --> + <application android:hasCode="false" /> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/> +</manifest> + diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json new file mode 100644 index 000000000000..1762fc6764cf --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v1.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 1 +} diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json new file mode 100644 index 000000000000..c5127b9c3023 --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v2.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 2 +} diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp new file mode 100644 index 000000000000..8a13dbc52c66 --- /dev/null +++ b/tests/TaskOrganizerTest/Android.bp @@ -0,0 +1,22 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "TaskOrganizerTest", + srcs: ["**/*.java"], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml new file mode 100644 index 000000000000..0cb6c10a7ff5 --- /dev/null +++ b/tests/TaskOrganizerTest/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.taskembed"> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <application> + <service android:name=".TaskOrganizerPipTest" + android:exported="true"> + </service> + </application> +</manifest> diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java new file mode 100644 index 000000000000..6ffa19d4ec98 --- /dev/null +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.taskembed; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.Service; +import android.app.WindowConfiguration; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.ITaskOrganizer; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +public class TaskOrganizerPipTest extends Service { + static final int PIP_WIDTH = 640; + static final int PIP_HEIGHT = 360; + + class PipOrgView extends SurfaceView implements SurfaceHolder.Callback { + PipOrgView(Context c) { + super(c); + getHolder().addCallback(this); + setZOrderOnTop(true); + } + @Override + public void surfaceCreated(SurfaceHolder holder) { + try { + ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer, + WindowConfiguration.WINDOWING_MODE_PINNED); + } catch (Exception e) { + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + + void reparentTask(IWindowContainer wc) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + SurfaceControl leash = null; + try { + leash = wc.getLeash(); + } catch (Exception e) { + // System server died.. oh well + } + t.reparent(leash, getSurfaceControl()) + .setPosition(leash, 0, 0) + .apply(); + } + } + + PipOrgView mPipView; + + class Organizer extends ITaskOrganizer.Stub { + public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) { + mPipView.reparentTask(wc); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); + try { + ActivityTaskManager.getService().applyContainerTransaction(wct); + } catch (Exception e) { + } + } + public void taskVanished(IWindowContainer wc) { + } + public void transactionReady(int id, SurfaceControl.Transaction t) { + } + } + + Organizer mOrganizer = new Organizer(); + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); + wlp.setTitle("TaskOrganizerPipTest"); + wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + + FrameLayout layout = new FrameLayout(this); + ViewGroup.LayoutParams lp = + new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT); + mPipView = new PipOrgView(this); + layout.addView(mPipView, lp); + + WindowManager wm = getSystemService(WindowManager.class); + wm.addView(layout, wlp); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } +} diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java index eed7159ffddc..ca4ba63142a2 100644 --- a/tests/net/common/java/android/net/CaptivePortalTest.java +++ b/tests/net/common/java/android/net/CaptivePortalTest.java @@ -44,6 +44,11 @@ public class CaptivePortalTest { } @Override + public void appRequest(final int request) throws RemoteException { + mCode = request; + } + + @Override public void logEvent(int eventId, String packageName) throws RemoteException { mCode = eventId; mPackageName = packageName; @@ -80,6 +85,12 @@ public class CaptivePortalTest { } @Test + public void testReevaluateNetwork() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork()); + assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED); + } + + @Test public void testLogEvent() { final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY, diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index 1e8d83c3c6cd..11d5b250d752 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -35,10 +35,10 @@ import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; -import android.net.NetworkFactory; import android.net.NetworkInfo; -import android.net.NetworkMisc; +import android.net.NetworkProvider; import android.net.NetworkSpecifier; import android.net.SocketKeepalive; import android.net.UidRange; @@ -114,7 +114,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) { super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag, wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore, - new NetworkMisc(), NetworkFactory.SerialNumber.NONE); + new NetworkAgentConfig(), NetworkProvider.ID_NONE); mWrapper = wrapper; } @@ -222,7 +222,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { @Override public Network getNetwork() { - return new Network(mNetworkAgent.netId); + return mNetworkAgent.network; } public void expectPreventReconnectReceived(long timeoutMs) { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b2d363e27839..1901a1db633b 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -575,7 +575,7 @@ public class ConnectivityServiceTest { } }; - assertEquals(na.netId, nmNetworkCaptor.getValue().netId); + assertEquals(na.network.netId, nmNetworkCaptor.getValue().netId); mNmCallbacks = nmCbCaptor.getValue(); mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor); diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 535298f9b09a..e863266c4b49 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -36,9 +36,8 @@ import android.net.IDnsResolver; import android.net.INetd; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkFactory; import android.net.NetworkInfo; -import android.net.NetworkMisc; +import android.net.NetworkProvider; import android.net.NetworkScore; import android.os.INetworkManagementService; import android.text.format.DateUtils; @@ -75,7 +74,6 @@ public class LingerMonitorTest { @Mock INetd mNetd; @Mock INetworkManagementService mNMS; @Mock Context mCtx; - @Mock NetworkMisc mMisc; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; @@ -358,8 +356,8 @@ public class LingerMonitorTest { NetworkScore ns = new NetworkScore(); ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, - caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS, - NetworkFactory.SerialNumber.NONE); + caps, ns, mCtx, null, null /* config */, mConnService, mNetd, mDnsResolver, mNMS, + NetworkProvider.ID_NONE); 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 b709af1a02f1..9b248878fe96 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -33,8 +33,8 @@ import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NetworkAgentConfig; import android.net.NetworkInfo; -import android.net.NetworkMisc; import android.os.Handler; import android.os.INetworkManagementService; import android.os.test.TestLooper; @@ -63,7 +63,6 @@ public class Nat464XlatTest { static final int NETID = 42; @Mock ConnectivityService mConnectivity; - @Mock NetworkMisc mMisc; @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNms; @@ -72,6 +71,7 @@ public class Nat464XlatTest { TestLooper mLooper; Handler mHandler; + NetworkAgentConfig mAgentConfig = new NetworkAgentConfig(); Nat464Xlat makeNat464Xlat() { return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { @@ -93,7 +93,7 @@ public class Nat464XlatTest { mNai.networkInfo = new NetworkInfo(null); mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); when(mNai.connService()).thenReturn(mConnectivity); - when(mNai.netMisc()).thenReturn(mMisc); + when(mNai.netAgentConfig()).thenReturn(mAgentConfig); when(mNai.handler()).thenReturn(mHandler); when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig); @@ -104,7 +104,7 @@ public class Nat464XlatTest { String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), - mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); assertEquals(msg, expected, Nat464Xlat.requiresClat(nai)); } @@ -113,7 +113,7 @@ public class Nat464XlatTest { String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), - mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai)); } @@ -151,11 +151,11 @@ public class Nat464XlatTest { assertRequiresClat(true, mNai); assertShouldStartClat(true, mNai); - mMisc.skip464xlat = true; + mAgentConfig.skip464xlat = true; assertRequiresClat(false, mNai); assertShouldStartClat(false, mNai); - mMisc.skip464xlat = false; + mAgentConfig.skip464xlat = false; assertRequiresClat(true, mNai); assertShouldStartClat(true, mNai); diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 6de068e48a38..a9e0b9abba9c 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -80,6 +80,7 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; @@ -102,6 +103,7 @@ import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; import com.android.testutils.HandlerUtilsKt; +import com.android.testutils.TestableNetworkStatsProvider; import libcore.io.IoUtils; @@ -1001,6 +1003,88 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mService.unregisterUsageRequest(unknownRequest); } + @Test + public void testStatsProviderUpdateStats() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); + + // Verifies that one requestStatsUpdate will be called during iface update. + provider.expectStatsUpdate(0 /* unused */); + + // Create some initial traffic and report to the service. + incrementCurrentTime(HOUR_IN_MILLIS); + final NetworkStats expectedStats = new NetworkStats(0L, 1) + .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 128L, 2L, 128L, 2L, 1L)) + .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 64L, 1L, 64L, 1L, 1L)); + cb.onStatsUpdated(0 /* unused */, expectedStats, expectedStats); + + // Make another empty mutable stats object. This is necessary since the new NetworkStats + // object will be used to compare with the old one in NetworkStatsRecoder, two of them + // cannot be the same object. + expectNetworkStatsUidDetail(buildEmptyStats()); + + forcePollAndWaitForIdle(); + + // Verifies that one requestStatsUpdate and setAlert will be called during polling. + provider.expectStatsUpdate(0 /* unused */); + provider.expectSetAlert(MB_IN_BYTES); + + // Verifies that service recorded history, does not verify uid tag part. + assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1); + + // Verifies that onStatsUpdated updates the stats accordingly. + final NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(2, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L); + + // Verifies that unregister the callback will remove the provider from service. + cb.unregister(); + forcePollAndWaitForIdle(); + provider.assertNoCallback(); + } + + @Test + public void testStatsProviderSetAlert() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)}; + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + // Simulates alert quota of the provider has been reached. + cb.onAlertReached(); + HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT); + + // Verifies that polling is triggered by alert reached. + provider.expectStatsUpdate(0 /* unused */); + // Verifies that global alert will be re-armed. + provider.expectSetAlert(MB_IN_BYTES); + } + private static File getBaseDir(File statsDir) { File baseDir = new File(statsDir, "netstats"); baseDir.mkdirs(); @@ -1102,6 +1186,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private void expectSettings(long persistBytes, long bucketDuration, long deleteAge) throws Exception { when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS); + when(mSettings.getPollDelay()).thenReturn(0L); when(mSettings.getSampleEnabled()).thenReturn(true); final Config config = new Config(bucketDuration, deleteAge, deleteAge); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 3623b1112bc6..469128b1e50b 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -800,7 +800,12 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } // This is a normal reference. - return util::make_unique<Reference>(data, ref_type); + auto reference = util::make_unique<Reference>(data, ref_type); + if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE || + res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) { + reference->is_dynamic = true; + } + return reference; } break; } diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index c016cb44af00..b08bf9a1ff17 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -109,6 +109,20 @@ TEST(ResourceUtilsTest, ParsePrivateReference) { EXPECT_TRUE(private_ref); } +TEST(ResourceUtilsTest, ParseBinaryDynamicReference) { + android::Res_value value = {}; + value.data = util::HostToDevice32(0x01); + value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE; + std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId, + android::ConfigDescription(), + android::ResStringPool(), value, + nullptr); + + Reference* ref = ValueCast<Reference>(item.get()); + EXPECT_TRUE(ref->is_dynamic); + EXPECT_EQ(ref->id.value().id, 0x01); +} + TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool private_ref = false; diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 7498e132d943..8a2f5afa7255 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -269,6 +269,11 @@ message CompoundValue { } } +// Message holding a boolean, so it can be optionally encoded. +message Boolean { + bool value = 1; +} + // A value that is a reference to another resource. This reference can be by name or resource ID. message Reference { enum Type { @@ -289,6 +294,9 @@ message Reference { // Whether this reference is referencing a private resource (@*package:type/entry). bool private = 4; + + // Whether this reference is dynamic. + Boolean is_dynamic = 5; } // A value that represents an ID. This is just a placeholder, as ID values are used to occupy a diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 4555caafb478..5b6935bafe71 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -895,7 +895,7 @@ class Linker { // android:versionCode from the framework AndroidManifest.xml. ExtractCompileSdkVersions(asset_source->GetAssetManager()); } - } else if (asset_source->IsPackageDynamic(entry.first)) { + } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) { final_table_.included_packages_[entry.first] = entry.second; } } diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index efbf636878f2..4cd6e930915d 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -634,6 +634,7 @@ static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* o std::string* out_error) { out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type()); out_ref->private_reference = pb_ref.private_(); + out_ref->is_dynamic = pb_ref.is_dynamic().value(); if (pb_ref.id() != 0) { out_ref->id = ResourceId(pb_ref.id()); diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index e4b3fce52166..d9f6c193fc2f 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -418,6 +418,9 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) pb_ref->set_private_(ref.private_reference); pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type)); + if (ref.is_dynamic) { + pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic); + } } template <typename T> diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index e7f23302652c..61a8335e17a7 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -608,4 +608,41 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { ASSERT_FALSE(search_result.value().entry->overlayable_item); } +TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) { + Reference ref(ResourceId(0x00010001)); + ref.is_dynamic = true; + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_TRUE(pb_item.ref().is_dynamic().value()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_TRUE(actual_ref->is_dynamic); +} + +TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) { + Reference ref(ResourceId(0x00010001)); + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_FALSE(pb_item.ref().has_is_dynamic()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_FALSE(actual_ref->is_dynamic); +} + } // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 83e20b5833b9..897fa80ffedb 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -245,7 +245,8 @@ std::map<size_t, std::string> AssetManagerSymbolSource::GetAssignedPackageIds() return package_map; } -bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const { +bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, + const std::string& package_name) const { if (packageId == 0) { return true; } @@ -253,7 +254,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const { for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) { for (const std::unique_ptr<const android::LoadedPackage>& loaded_package : assets->GetLoadedArsc()->GetPackages()) { - if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) { + if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) { return true; } } @@ -328,12 +329,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( bool found = false; ResourceId res_id = 0; uint32_t type_spec_flags; + ResourceName real_name; // There can be mangled resources embedded within other packages. Here we will // look into each package and look-up the mangled name until we find the resource. asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool { - ResourceName real_name(name.package, name.type, name.entry); - + real_name = ResourceName(name.package, name.type, name.entry); if (package_name != name.package) { real_name.entry = mangled_entry; real_name.package = package_name; @@ -353,12 +354,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( } std::unique_ptr<SymbolTable::Symbol> s; - if (name.type == ResourceType::kAttr) { + if (real_name.type == ResourceType::kAttr) { s = LookupAttributeInTable(asset_manager_, res_id); } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = res_id; - s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id()); + s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package); } if (s) { @@ -406,7 +407,7 @@ 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()); + s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package); } if (s) { diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index 6997cd6714a8..06eaf63ad442 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -194,7 +194,7 @@ class AssetManagerSymbolSource : public ISymbolSource { bool AddAssetPath(const android::StringPiece& path); std::map<size_t, std::string> GetAssignedPackageIds() const; - bool IsPackageDynamic(uint32_t packageId) const; + bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const; std::unique_ptr<SymbolTable::Symbol> FindByName( const ResourceName& name) override; diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py index 46105f4d66b0..0b2077d9bba0 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ b/tools/hiddenapi/generate_hiddenapi_lists.py @@ -149,7 +149,12 @@ def extract_package(signature): The package name of the class containing the field/method. """ full_class_name = signature.split(";->")[0] - package_name = full_class_name[1:full_class_name.rindex("/")] + # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy + if (full_class_name[0] != "L"): + raise ValueError("Expected to start with 'L': %s" % full_class_name) + full_class_name = full_class_name[1:] + # If full_class_name doesn't contain '/', then package_name will be ''. + package_name = full_class_name.rpartition("/")[0] return package_name.replace('/', '.') class FlagsDict: diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp index 79dce4a8ce09..7b2ca9a65242 100644 --- a/tools/lock_agent/Android.bp +++ b/tools/lock_agent/Android.bp @@ -25,6 +25,7 @@ cc_binary_host { srcs: ["agent.cpp"], static_libs: [ "libbase", + "liblog", "libz", "slicer", ], diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 0b82a3dcebc4..7bbac137f998 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -291,6 +291,15 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, } if (field->options().GetExtension(os::statsd::state_field_option).option() == + os::statsd::StateField::PRIMARY_FIELD_FIRST_UID) { + if (javaType != JAVA_TYPE_ATTRIBUTION_CHAIN) { + errorCount++; + } else { + atomDecl->primaryFields.push_back(FIRST_UID_IN_CHAIN_ID); + } + } + + if (field->options().GetExtension(os::statsd::state_field_option).option() == os::statsd::StateField::EXCLUSIVE) { if (javaType == JAVA_TYPE_UNKNOWN || javaType == JAVA_TYPE_ATTRIBUTION_CHAIN || diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index 3efdd520d7f5..87d4d5db0cee 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -36,6 +36,8 @@ using google::protobuf::FieldDescriptor; const int PULL_ATOM_START_ID = 10000; +const int FIRST_UID_IN_CHAIN_ID = 0; + /** * The types for atom parameters. */ diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp index 54a9982bb5c2..66ae96401974 100644 --- a/tools/stats_log_api_gen/atoms_info_writer.cpp +++ b/tools/stats_log_api_gen/atoms_info_writer.cpp @@ -25,6 +25,8 @@ namespace android { namespace stats_log_api_gen { static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) { + fprintf(out, "static int FIRST_UID_IN_CHAIN = 0;\n"); + fprintf(out, "struct StateAtomFieldOptions {\n"); fprintf(out, " std::vector<int> primaryFields;\n"); fprintf(out, " int exclusiveField;\n"); diff --git a/wifi/Android.bp b/wifi/Android.bp index 180368cbd9f7..70c9befce66a 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -32,6 +32,7 @@ filegroup { // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache // to a separate package. "java/android/net/wifi/WifiNetworkScoreCache.java", + "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java", "java/android/net/wifi/wificond/*.java", ":libwificond_ipc_aidl", ], @@ -49,20 +50,47 @@ test_access_hidden_api_whitelist = [ "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests", "//external/robolectric-shadows:__subpackages__", + "//frameworks/base/packages/SettingsLib/tests/integ", + "//external/sl4a:__subpackages__", ] +// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility +// classes before they are renamed. java_library { - name: "framework-wifi", + name: "framework-wifi-pre-jarjar", // TODO(b/140299412) should be core_current once we build against framework-system-stubs sdk_version: "core_platform", + static_libs: [ + "framework-wifi-util-lib", + "android.hardware.wifi-V1.0-java-constants", + ], libs: [ // TODO(b/140299412) should be framework-system-stubs once we fix all @hide dependencies "framework-minus-apex", - "unsupportedappusage", + "framework-annotations-lib", + "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage + "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage ], srcs: [ ":framework-wifi-updatable-sources", ], + installable: false, + visibility: [ + "//frameworks/opt/net/wifi/service", + "//frameworks/opt/net/wifi/tests/wifitests", + ], +} + +// post-jarjar version of framework-wifi +java_library { + name: "framework-wifi", + // TODO(b/140299412) should be core_current once we build against framework-system-stubs + sdk_version: "core_platform", + static_libs: [ + "framework-wifi-pre-jarjar", + ], + jarjar_rules: ":wifi-jarjar-rules", + installable: true, optimize: { enabled: false @@ -121,3 +149,8 @@ java_defaults { ], visibility: test_access_hidden_api_whitelist, } + +filegroup { + name: "wifi-jarjar-rules", + srcs: ["jarjar-rules.txt"], +} diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt new file mode 100644 index 000000000000..8f720407f4d7 --- /dev/null +++ b/wifi/jarjar-rules.txt @@ -0,0 +1,40 @@ +rule android.net.InterfaceConfigurationParcel* @0 +rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1 + +# We don't jar-jar the entire package because, we still use some classes (like +# AsyncChannel in com.android.internal.util) from these packages which are not +# inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future). +rule com.android.internal.util.FastXmlSerializer* com.android.server.x.wifi.util.FastXmlSerializer@1 +rule com.android.internal.util.HexDump* com.android.server.x.wifi.util.HexDump@1 +rule com.android.internal.util.IState* com.android.server.x.wifi.util.IState@1 +rule com.android.internal.util.MessageUtils* com.android.server.x.wifi.util.MessageUtils@1 +rule com.android.internal.util.State* com.android.server.x.wifi.util.State@1 +rule com.android.internal.util.StateMachine* com.android.server.x.wifi.util.StateMachine@1 +rule com.android.internal.util.WakeupMessage* com.android.server.x.wifi.util.WakeupMessage@1 + +rule android.util.BackupUtils* com.android.server.x.wifi.util.BackupUtils@1 +rule android.util.LocalLog* com.android.server.x.wifi.util.LocalLog@1 +rule android.util.Rational* com.android.server.x.wifi.util.Rational@1 + +rule android.os.BasicShellCommandHandler* com.android.server.x.wifi.os.BasicShellCommandHandler@1 + +# Use our statically linked bouncy castle library +rule org.bouncycastle.** com.android.server.x.wifi.bouncycastle.@1 +# Use our statically linked protobuf library +rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1 +# use statically linked SystemMessageProto +rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1 +# Use our statically linked PlatformProperties library +rule android.sysprop.** com.android.server.x.wifi.sysprop.@1 + + +# used by both framework-wifi and wifi-service +rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1 +rule android.content.pm.ParceledListSlice* android.x.net.wifi.util.ParceledListSlice@1 +rule android.net.shared.Inet4AddressUtils* android.x.net.wifi.util.Inet4AddressUtils@1 +rule android.os.HandlerExecutor* android.x.net.wifi.util.HandlerExecutor@1 +rule android.telephony.Annotation* android.x.net.wifi.util.TelephonyAnnotation@1 +rule com.android.internal.util.AsyncChannel* android.x.net.wifi.util.AsyncChannel@1 +rule com.android.internal.util.AsyncService* android.x.net.wifi.util.AsyncService@1 +rule com.android.internal.util.Preconditions* android.x.net.wifi.util.Preconditions@1 +rule com.android.internal.util.Protocol* android.x.net.wifi.util.Protocol@1 diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl index 482b4910921d..f81bcb9e06d7 100644 --- a/wifi/java/android/net/wifi/ISoftApCallback.aidl +++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl @@ -55,9 +55,17 @@ oneway interface ISoftApCallback /** - * Service to manager callback providing information of softap. + * Service to manager callback providing capability of softap. * * @param capability is the softap capability. {@link SoftApCapability} */ void onCapabilityChanged(in SoftApCapability capability); + + /** + * Service to manager callback providing blocked client of softap with specific reason code. + * + * @param client the currently blocked client. + * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason} + */ + void onBlockedClientConnecting(in WifiClient client, int blockedReason); } diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 71942f0f80f4..f490766559de 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -250,4 +250,6 @@ interface IWifiManager void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName); int calculateSignalLevel(int rssi); + + List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults); } diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index c6aca07ebe94..341330587614 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -23,6 +23,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -793,10 +795,19 @@ public class ScanResult implements Parcelable { } } - /** empty scan result + /** + * Construct an empty scan result. * - * {@hide} - * */ + * Test code has a need to construct a ScanResult in a specific state. + * (Note that mocking using Mockito does not work if the object needs to be parceled and + * unparceled.) + * Export a @SystemApi default constructor to allow tests to construct an empty ScanResult + * object. The test can then directly set the fields it cares about. + * + * @hide + */ + @SystemApi + @VisibleForTesting public ScanResult() { } diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 2b7f8af728d7..a77d30a18817 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -32,6 +32,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -182,6 +185,22 @@ public final class SoftApConfiguration implements Parcelable { private final @SecurityType int mSecurityType; /** + * The flag to indicate client need to authorize by user + * when client is connecting to AP. + */ + private final boolean mClientControlByUser; + + /** + * The list of blocked client that can't associate to the AP. + */ + private final List<MacAddress> mBlockedClientList; + + /** + * The list of allowed client that can associate to the AP. + */ + private final List<MacAddress> mAllowedClientList; + + /** * Delay in milliseconds before shutting down soft AP when * there are no connected devices. */ @@ -219,7 +238,9 @@ public final class SoftApConfiguration implements Parcelable { /** Private constructor for Builder and Parcelable implementation. */ private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid, @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel, - @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) { + @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis, + boolean clientControlByUser, @NonNull List<MacAddress> blockedList, + @NonNull List<MacAddress> allowedList) { mSsid = ssid; mBssid = bssid; mPassphrase = passphrase; @@ -229,6 +250,9 @@ public final class SoftApConfiguration implements Parcelable { mSecurityType = securityType; mMaxNumberOfClients = maxNumberOfClients; mShutdownTimeoutMillis = shutdownTimeoutMillis; + mClientControlByUser = clientControlByUser; + mBlockedClientList = new ArrayList<>(blockedList); + mAllowedClientList = new ArrayList<>(allowedList); } @Override @@ -248,13 +272,17 @@ public final class SoftApConfiguration implements Parcelable { && mChannel == other.mChannel && mSecurityType == other.mSecurityType && mMaxNumberOfClients == other.mMaxNumberOfClients - && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis; + && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis + && mClientControlByUser == other.mClientControlByUser + && Objects.equals(mBlockedClientList, other.mBlockedClientList) + && Objects.equals(mAllowedClientList, other.mAllowedClientList); } @Override public int hashCode() { return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid, - mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis); + mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis, + mClientControlByUser, mBlockedClientList, mAllowedClientList); } @Override @@ -270,6 +298,9 @@ public final class SoftApConfiguration implements Parcelable { sbuf.append(" \n SecurityType=").append(getSecurityType()); sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients); sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis); + sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser); + sbuf.append(" \n BlockedClientList=").append(mBlockedClientList); + sbuf.append(" \n AllowedClientList=").append(mAllowedClientList); return sbuf.toString(); } @@ -284,6 +315,9 @@ public final class SoftApConfiguration implements Parcelable { dest.writeInt(mSecurityType); dest.writeInt(mMaxNumberOfClients); dest.writeInt(mShutdownTimeoutMillis); + dest.writeBoolean(mClientControlByUser); + dest.writeTypedList(mBlockedClientList); + dest.writeTypedList(mAllowedClientList); } @Override @@ -299,7 +333,9 @@ public final class SoftApConfiguration implements Parcelable { in.readString(), in.readParcelable(MacAddress.class.getClassLoader()), in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(), - in.readInt(), in.readInt()); + in.readInt(), in.readInt(), in.readBoolean(), + in.createTypedArrayList(MacAddress.CREATOR), + in.createTypedArrayList(MacAddress.CREATOR)); } @Override @@ -387,6 +423,34 @@ public final class SoftApConfiguration implements Parcelable { } /** + * Returns a flag indicating whether clients need to be pre-approved by the user. + * (true: authorization required) or not (false: not required). + * {@link Builder#enableClientControlByUser(Boolean)}. + */ + public boolean isClientControlByUserEnabled() { + return mClientControlByUser; + } + + /** + * Returns List of clients which aren't allowed to associate to the AP. + * + * Clients are configured using {@link Builder#setClientList(List, List)} + */ + @NonNull + public List<MacAddress> getBlockedClientList() { + return mBlockedClientList; + } + + /** + * List of clients which are allowed to associate to the AP. + * Clients are configured using {@link Builder#setClientList(List, List)} + */ + @NonNull + public List<MacAddress> getAllowedClientList() { + return mAllowedClientList; + } + + /** * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a * Soft AP. * @@ -403,6 +467,9 @@ public final class SoftApConfiguration implements Parcelable { private int mMaxNumberOfClients; private int mSecurityType; private int mShutdownTimeoutMillis; + private boolean mClientControlByUser; + private List<MacAddress> mBlockedClientList; + private List<MacAddress> mAllowedClientList; /** * Constructs a Builder with default values (see {@link Builder}). @@ -417,6 +484,9 @@ public final class SoftApConfiguration implements Parcelable { mMaxNumberOfClients = 0; mSecurityType = SECURITY_TYPE_OPEN; mShutdownTimeoutMillis = 0; + mClientControlByUser = false; + mBlockedClientList = new ArrayList<>(); + mAllowedClientList = new ArrayList<>(); } /** @@ -434,6 +504,9 @@ public final class SoftApConfiguration implements Parcelable { mMaxNumberOfClients = other.mMaxNumberOfClients; mSecurityType = other.mSecurityType; mShutdownTimeoutMillis = other.mShutdownTimeoutMillis; + mClientControlByUser = other.mClientControlByUser; + mBlockedClientList = new ArrayList<>(other.mBlockedClientList); + mAllowedClientList = new ArrayList<>(other.mAllowedClientList); } /** @@ -445,7 +518,8 @@ public final class SoftApConfiguration implements Parcelable { public SoftApConfiguration build() { return new SoftApConfiguration(mSsid, mBssid, mPassphrase, mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients, - mShutdownTimeoutMillis); + mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList, + mAllowedClientList); } /** @@ -662,5 +736,82 @@ public final class SoftApConfiguration implements Parcelable { mShutdownTimeoutMillis = timeoutMillis; return this; } + + /** + * Configure the Soft AP to require manual user control of client association. + * If disabled (the default) then any client can associate to this Soft AP using the + * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier, + * or user limited - using {@link #setMaxNumberOfClients(int)}). + * + * If manual user control is enabled then clients will be accepted, rejected, or require + * a user approval based on the configuration provided by + * {@link #setClientList(List, List)}. + * + * <p> + * This method requires hardware support. Hardware support can be determined using + * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and + * {@link SoftApCapability#isFeatureSupported(int)} + * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} + * + * <p> + * If the method is called on a device without hardware support then starting the soft AP + * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with + * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}. + * + * <p> + * <li>If not set, defaults to false (i.e The authoriztion is not required).</li> + * + * @param enabled true for enabling the control by user, false otherwise. + * @return Builder for chaining. + */ + @NonNull + public Builder enableClientControlByUser(boolean enabled) { + mClientControlByUser = enabled; + return this; + } + + + /** + * This method together with {@link enableClientControlByUser(boolean)} control client + * connections to the AP. If {@link enableClientControlByUser(false)} is configured than + * this API has no effect and clients are allowed to associate to the AP (within limit of + * max number of clients). + * + * If {@link enableClientControlByUser(true)} is configured then this API configures + * 2 lists: + * <ul> + * <li>List of clients which are blocked. These are rejected.</li> + * <li>List of clients which are explicitly allowed. These are auto-accepted.</li> + * </ul> + * + * <p> + * All other clients which attempt to associate, whose MAC addresses are on neither list, + * are: + * <ul> + * <li>Rejected</li> + * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)} + * is issued (which allows the user to add them to the allowed client list if desired).<li> + * </ul> + * + * @param blockedClientList list of clients which are not allowed to associate to the AP. + * @param allowedClientList list of clients which are allowed to associate to the AP + * without user pre-approval. + * @return Builder for chaining. + */ + @NonNull + public Builder setClientList(@NonNull List<MacAddress> blockedClientList, + @NonNull List<MacAddress> allowedClientList) { + mBlockedClientList = new ArrayList<>(blockedClientList); + mAllowedClientList = new ArrayList<>(allowedClientList); + Iterator<MacAddress> iterator = mAllowedClientList.iterator(); + while (iterator.hasNext()) { + MacAddress client = iterator.next(); + int index = mBlockedClientList.indexOf(client); + if (index != -1) { + throw new IllegalArgumentException("A MacAddress exist in both list"); + } + } + return this; + } } } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index f4c5b9168cd0..ee2b57535b50 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -39,6 +39,8 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -1208,22 +1210,27 @@ public class WifiConfiguration implements Parcelable { */ @SystemApi public static class NetworkSelectionStatus { - // Quality Network Selection Status enable, temporary disabled, permanently disabled + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "NETWORK_SELECTION_", + value = { + NETWORK_SELECTION_ENABLED, + NETWORK_SELECTION_TEMPORARY_DISABLED, + NETWORK_SELECTION_PERMANENTLY_DISABLED}) + public @interface NetworkEnabledStatus {} /** - * This network is allowed to join Quality Network Selection - * @hide + * This network will be considered as a potential candidate to connect to during network + * selection. */ public static final int NETWORK_SELECTION_ENABLED = 0; /** - * network was temporary disabled. Can be re-enabled after a time period expire - * @hide + * This network was temporary disabled. May be re-enabled after a time out. */ - public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; + public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; /** - * network was permanently disabled. - * @hide + * This network was permanently disabled. */ - public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; + public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; /** * Maximum Network selection status * @hide @@ -1455,6 +1462,7 @@ public class WifiConfiguration implements Parcelable { * Network selection status, should be in one of three status: enable, temporaily disabled * or permanently disabled */ + @NetworkEnabledStatus private int mStatus; /** @@ -1635,6 +1643,56 @@ public class WifiConfiguration implements Parcelable { } /** + * NetworkSelectionStatus exports an immutable public API. + * However, test code has a need to construct a NetworkSelectionStatus in a specific state. + * (Note that mocking using Mockito does not work if the object needs to be parceled and + * unparceled.) + * Export a @SystemApi Builder to allow tests to construct a NetworkSelectionStatus object + * in the desired state, without sacrificing NetworkSelectionStatus's immutability. + */ + @VisibleForTesting + public static final class Builder { + private final NetworkSelectionStatus mNetworkSelectionStatus = + new NetworkSelectionStatus(); + + /** + * Set the current network selection status. + * One of: + * {@link #NETWORK_SELECTION_ENABLED}, + * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED}, + * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED} + * @see NetworkSelectionStatus#getNetworkSelectionStatus() + */ + @NonNull + public Builder setNetworkSelectionStatus(@NetworkEnabledStatus int status) { + mNetworkSelectionStatus.setNetworkSelectionStatus(status); + return this; + } + + /** + * + * Set the current network's disable reason. + * One of the {@link #NETWORK_SELECTION_ENABLE} or DISABLED_* constants. + * e.g. {@link #DISABLED_ASSOCIATION_REJECTION}. + * @see NetworkSelectionStatus#getNetworkSelectionDisableReason() + */ + @NonNull + public Builder setNetworkSelectionDisableReason( + @NetworkSelectionDisableReason int reason) { + mNetworkSelectionStatus.setNetworkSelectionDisableReason(reason); + return this; + } + + /** + * Build a NetworkSelectionStatus object. + */ + @NonNull + public NetworkSelectionStatus build() { + return mNetworkSelectionStatus; + } + } + + /** * Get the network disable reason string for a reason code (for debugging). * @param reason specific error reason. One of the {@link #NETWORK_SELECTION_ENABLE} or * DISABLED_* constants e.g. {@link #DISABLED_ASSOCIATION_REJECTION}. @@ -1660,10 +1718,13 @@ public class WifiConfiguration implements Parcelable { } /** - * get current network network selection status - * @return return current network network selection status - * @hide + * Get the current network network selection status. + * One of: + * {@link #NETWORK_SELECTION_ENABLED}, + * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED}, + * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED} */ + @NetworkEnabledStatus public int getNetworkSelectionStatus() { return mStatus; } @@ -1965,10 +2026,11 @@ public class WifiConfiguration implements Parcelable { } /** - * Set the network selection status + * Set the network selection status. * @hide */ - public void setNetworkSelectionStatus(NetworkSelectionStatus status) { + @SystemApi + public void setNetworkSelectionStatus(@NonNull NetworkSelectionStatus status) { mNetworkSelectionStatus = status; } diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java index 41f7c6e2bb0d..5edcc2df3804 100644 --- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java +++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java @@ -1028,8 +1028,10 @@ public class WifiEnterpriseConfig implements Parcelable { } /** - * @hide + * Get the client private key as supplied in {@link #setClientKeyEntryWithCertificateChain}, or + * null if unset. */ + @Nullable public PrivateKey getClientPrivateKey() { return mClientPrivateKey; } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 62337cbf7e22..51b15afec3ab 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -17,6 +17,7 @@ package android.net.wifi; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -27,6 +28,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.annotations.VisibleForTesting; + import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; @@ -356,6 +359,72 @@ public class WifiInfo implements Parcelable { } } + /** + * WifiInfo exports an immutable public API. + * However, test code has a need to construct a WifiInfo in a specific state. + * (Note that mocking using Mockito does not work if the object needs to be parceled and + * unparceled.) + * Export a @SystemApi Builder to allow tests to construct a WifiInfo object + * in the desired state, without sacrificing WifiInfo's immutability. + * + * @hide + */ + // This builder was not made public to reduce confusion for external developers as there are + // no legitimate uses for this builder except for testing. + @SystemApi + @VisibleForTesting + public static final class Builder { + private final WifiInfo mWifiInfo = new WifiInfo(); + + /** + * Set the SSID, in the form of a raw byte array. + * @see WifiInfo#getSSID() + */ + @NonNull + public Builder setSsid(@NonNull byte[] ssid) { + mWifiInfo.setSSID(WifiSsid.createFromByteArray(ssid)); + return this; + } + + /** + * Set the BSSID. + * @see WifiInfo#getBSSID() + */ + @NonNull + public Builder setBssid(@NonNull String bssid) { + mWifiInfo.setBSSID(bssid); + return this; + } + + /** + * Set the RSSI, in dBm. + * @see WifiInfo#getRssi() + */ + @NonNull + public Builder setRssi(int rssi) { + mWifiInfo.setRssi(rssi); + return this; + } + + /** + * Set the network ID. + * @see WifiInfo#getNetworkId() + */ + @NonNull + public Builder setNetworkId(int networkId) { + mWifiInfo.setNetworkId(networkId); + return this; + } + + /** + * Build a WifiInfo object. + */ + @NonNull + public WifiInfo build() { + return mWifiInfo; + } + } + /** @hide */ public void setSSID(WifiSsid wifiSsid) { mWifiSsid = wifiSsid; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index e870481ccf26..a8a31eba303c 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -233,16 +233,20 @@ public class WifiManager { public @interface SuggestionConnectionStatusCode {} /** - * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently - * @hide + * Broadcast intent action indicating whether Wi-Fi scanning is currently available. + * Available extras: + * - {@link #EXTRA_SCAN_AVAILABLE} */ - public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available"; + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_WIFI_SCAN_AVAILABLE = + "android.net.wifi.action.WIFI_SCAN_AVAILABLE"; /** - * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED - * @hide + * A boolean extra indicating whether scanning is currently available. + * Sent in the broadcast {@link #ACTION_WIFI_SCAN_AVAILABLE}. + * Its value is true if scanning is currently available, false otherwise. */ - public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled"; + public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE"; /** * Broadcast intent action indicating that the credential of a Wi-Fi network @@ -666,7 +670,8 @@ public class WifiManager { public @interface SapStartFailure {} /** - * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}. + * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and + * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}. * * @hide */ @@ -691,6 +696,37 @@ public class WifiManager { @SystemApi public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; + + /** @hide */ + @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = { + SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER, + SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SapClientBlockedReason {} + + /** + * If Soft Ap client is blocked, this reason code means that client doesn't exist in the + * specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)} + * and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)} + * is configured as well. + * @hide + */ + @SystemApi + public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; + + /** + * If Soft Ap client is blocked, this reason code means that no more clients can be + * associated to this AP since it reached maximum capacity. The maximum capacity is + * the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and + * {@link SoftApCapability#getMaxSupportedClients} which get from + * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}. + * + * @hide + */ + @SystemApi + public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"IFACE_IP_MODE_"}, value = { @@ -1366,6 +1402,36 @@ public class WifiManager { } /** + * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion} + * matching the given list of {@link ScanResult}. + * + * An available {@link WifiNetworkSuggestion} must satisfy: + * <ul> + * <li> Matching one of the {@link ScanResult} from the given list. + * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set + * to true. + * </ul> + * + * @param scanResults a list of scanResult. + * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion} + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD + }) + @NonNull + public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( + @NonNull List<ScanResult> scanResults) { + try { + return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given * list of ScanResult. * @@ -3199,8 +3265,17 @@ public class WifiManager { /** * Sets the Wi-Fi AP Configuration. * + * If the API is called while the soft AP is enabled, the configuration will apply to + * the current soft AP if the new configuration only includes + * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} + * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)} + * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)} + * or {@link SoftApConfiguration.Builder#setClientList(List, List)}. + * + * Otherwise, the configuration changes will be applied when the Soft AP is next started + * (the framework will not stop/start the AP). + * * @param softApConfig A valid SoftApConfiguration specifying the configuration of the SAP. - * @return {@code true} if the operation succeeded, {@code false} otherwise * * @hide @@ -3430,7 +3505,8 @@ public class WifiManager { * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} * @param failureReason reason when in failed state. One of * {@link #SAP_START_FAILURE_GENERAL}, - * {@link #SAP_START_FAILURE_NO_CHANNEL} + * {@link #SAP_START_FAILURE_NO_CHANNEL}, + * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION} */ default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {} @@ -3459,6 +3535,22 @@ public class WifiManager { // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported // client number) to the UI. } + + /** + * Called when client trying to connect but device blocked the client with specific reason. + * + * Can be used to ask user to update client to allowed list or blocked list + * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or + * indicate the block due to maximum supported client number limitation when reason is + * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}. + * + * @param client the currently blocked client. + * @param blockedReason one of blocked reason from {@link SapClientBlockedReason} + */ + default void onBlockedClientConnecting(@NonNull WifiClient client, + @SapClientBlockedReason int blockedReason) { + // Do nothing: can be used to ask user to update client to allowed list or blocked list. + } } /** @@ -3525,6 +3617,19 @@ public class WifiManager { mCallback.onCapabilityChanged(capability); }); } + + @Override + public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client + + " with reason = " + blockedReason); + } + + Binder.clearCallingIdentity(); + mExecutor.execute(() -> { + mCallback.onBlockedClientConnecting(client, blockedReason); + }); + } } /** diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 9c1475ffc8cd..c0e089090dc9 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -116,12 +116,12 @@ public final class WifiNetworkSuggestion implements Parcelable { /** * Whether this network is shared credential with user to allow user manually connect. */ - private boolean mIsUserAllowed; + private boolean mIsSharedWithUser; /** - * Whether the setIsUserAllowedToManuallyConnect have been called. + * Whether the setCredentialSharedWithUser have been called. */ - private boolean mIsUserAllowedBeenSet; + private boolean mIsSharedWithUserSet; /** * Pre-shared key for use with WAPI-PSK networks. */ @@ -146,8 +146,8 @@ public final class WifiNetworkSuggestion implements Parcelable { mIsAppInteractionRequired = false; mIsUserInteractionRequired = false; mIsMetered = false; - mIsUserAllowed = true; - mIsUserAllowedBeenSet = false; + mIsSharedWithUser = true; + mIsSharedWithUserSet = false; mPriority = UNASSIGNED_PRIORITY; mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; mWapiPskPassphrase = null; @@ -430,13 +430,13 @@ public final class WifiNetworkSuggestion implements Parcelable { * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure * networks and false for open networks.</li> * - * @param isAllowed {@code true} to indicate that the credentials may be used by the user to + * @param isShared {@code true} to indicate that the credentials may be used by the user to * manually connect to the network, {@code false} otherwise. * @return Instance of {@link Builder} to enable chaining of the builder method. */ - public @NonNull Builder setIsUserAllowedToManuallyConnect(boolean isAllowed) { - mIsUserAllowed = isAllowed; - mIsUserAllowedBeenSet = true; + public @NonNull Builder setCredentialSharedWithUser(boolean isShared) { + mIsSharedWithUser = isShared; + mIsSharedWithUserSet = true; return this; } @@ -602,11 +602,11 @@ public final class WifiNetworkSuggestion implements Parcelable { } wifiConfiguration = buildWifiConfiguration(); if (wifiConfiguration.isOpenNetwork()) { - if (mIsUserAllowedBeenSet && mIsUserAllowed) { + if (mIsSharedWithUserSet && mIsSharedWithUser) { throw new IllegalStateException("Open network should not be " - + "setIsUserAllowedToManuallyConnect to true"); + + "setCredentialSharedWithUser to true"); } - mIsUserAllowed = false; + mIsSharedWithUser = false; } } @@ -615,7 +615,7 @@ public final class WifiNetworkSuggestion implements Parcelable { mPasspointConfiguration, mIsAppInteractionRequired, mIsUserInteractionRequired, - mIsUserAllowed); + mIsSharedWithUser); } } diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java new file mode 100755 index 000000000000..642dcb9211ae --- /dev/null +++ b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java @@ -0,0 +1,180 @@ +/* + * 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.net.wifi; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Class used to provide one time hooks for existing OEM devices to migrate their config store + * data to the wifi mainline module. + * <p> + * Note: + * <li> OEM's need to implement {@link #load()} only if their + * existing config store format or file locations differs from the vanilla AOSP implementation ( + * which is what the wifi mainline module understands). + * </li> + * <li> The wifi mainline module will invoke {@link #load()} method on every bootup, its + * the responsibility of the OEM implementation to ensure that this method returns non-null data + * only on the first bootup. Once the migration is done, the OEM can safely delete their config + * store files and then return null on any subsequent reboots. The first & only relevant invocation + * of {@link #load()} occurs when a previously released device upgrades to the wifi + * mainline module from an OEM implementation of the wifi stack. + * </li> + * @hide + */ +@SystemApi +public final class WifiOemConfigStoreMigrationHook { + /** + * Container for all the wifi config data to migrate. + */ + public static final class MigrationData implements Parcelable { + /** + * Builder to create instance of {@link MigrationData}. + */ + public static final class Builder { + private List<WifiConfiguration> mUserSavedNetworkConfigurations; + private SoftApConfiguration mUserSoftApConfiguration; + + public Builder() { + mUserSavedNetworkConfigurations = null; + mUserSoftApConfiguration = null; + } + + /** + * Sets the list of all user's saved network configurations parsed from OEM config + * store files. + * + * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing + * the list of user's saved networks + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setUserSavedNetworkConfigurations( + @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) { + checkNotNull(userSavedNetworkConfigurations); + mUserSavedNetworkConfigurations = userSavedNetworkConfigurations; + return this; + } + + /** + * Sets the user's softap configuration parsed from OEM config store files. + * + * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's + * SoftAp configuration + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setUserSoftApConfiguration( + @NonNull SoftApConfiguration userSoftApConfiguration) { + checkNotNull(userSoftApConfiguration); + mUserSoftApConfiguration = userSoftApConfiguration; + return this; + } + + /** + * Build an instance of {@link MigrationData}. + * + * @return Instance of {@link MigrationData}. + */ + public @NonNull MigrationData build() { + return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration); + } + } + + private final List<WifiConfiguration> mUserSavedNetworkConfigurations; + private final SoftApConfiguration mUserSoftApConfiguration; + + private MigrationData( + @Nullable List<WifiConfiguration> userSavedNetworkConfigurations, + @Nullable SoftApConfiguration userSoftApConfiguration) { + mUserSavedNetworkConfigurations = userSavedNetworkConfigurations; + mUserSoftApConfiguration = userSoftApConfiguration; + } + + public static final @NonNull Parcelable.Creator<MigrationData> CREATOR = + new Parcelable.Creator<MigrationData>() { + @Override + public MigrationData createFromParcel(Parcel in) { + List<WifiConfiguration> userSavedNetworkConfigurations = + in.readArrayList(null); + SoftApConfiguration userSoftApConfiguration = in.readParcelable(null); + return new MigrationData( + userSavedNetworkConfigurations, userSoftApConfiguration); + } + + @Override + public MigrationData[] newArray(int size) { + return new MigrationData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeList(mUserSavedNetworkConfigurations); + dest.writeParcelable(mUserSoftApConfiguration, flags); + } + + /** + * Returns list of all user's saved network configurations. + * + * Note: Only to be returned if there is any format change in how OEM persisted this info. + * @return List of {@link WifiConfiguration} representing the list of user's saved networks, + * or null if no migration necessary. + */ + @Nullable + public List<WifiConfiguration> getUserSavedNetworkConfigurations() { + return mUserSavedNetworkConfigurations; + } + + /** + * Returns user's softap configuration. + * + * Note: Only to be returned if there is any format change in how OEM persisted this info. + * @return {@link SoftApConfiguration} representing user's SoftAp configuration, + * or null if no migration necessary. + */ + @Nullable + public SoftApConfiguration getUserSoftApConfiguration() { + return mUserSoftApConfiguration; + } + } + + private WifiOemConfigStoreMigrationHook() { } + + /** + * Load data from OEM's config store. + * + * @return Instance of {@link MigrationData} for migrating data, null if no + * migration is necessary. + */ + @Nullable + public static MigrationData load() { + // Note: OEM's should add code to parse data from their config store format here! + return null; + } +} diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 2c39c32ac81e..4f602fac7a36 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -833,6 +833,7 @@ public class WifiScanner { * delivered to the listener. It is possible that onFullResult will not be called for all * results of the first scan if the listener was registered during the scan. * + * @param executor the Executor on which to run the callback. * @param listener specifies the object to report events to. This object is also treated as a * key for this request, and must also be specified to cancel the request. * Multiple requests should also not share this object. @@ -955,15 +956,32 @@ public class WifiScanner { * starts a single scan and reports results asynchronously * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} - * @param workSource WorkSource to blame for power usage * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. + * @param workSource WorkSource to blame for power usage */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) { + startScan(settings, null, listener, workSource); + } + + /** + * starts a single scan and reports results asynchronously + * @param settings specifies various parameters for the scan; for more information look at + * {@link ScanSettings} + * @param executor the Executor on which to run the callback. + * @param listener specifies the object to report events to. This object is also treated as a + * key for this scan, and must also be specified to cancel the scan. Multiple + * scans should also not share this object. + * @param workSource WorkSource to blame for power usage + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor, + ScanListener listener, WorkSource workSource) { Objects.requireNonNull(listener, "listener cannot be null"); - int key = addListener(listener); + int key = addListener(listener, executor); if (key == INVALID_KEY) return; validateChannel(); Bundle scanParams = new Bundle(); @@ -1029,16 +1047,17 @@ public class WifiScanner { * {@link ScanSettings} * @param pnoSettings specifies various parameters for PNO; for more information look at * {@link PnoSettings} + * @param executor the Executor on which to run the callback. * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. * {@hide} */ public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, - PnoScanListener listener) { + @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null"); - int key = addListener(listener); + int key = addListener(listener, executor); if (key == INVALID_KEY) return; validateChannel(); pnoSettings.isConnected = true; @@ -1057,10 +1076,10 @@ public class WifiScanner { */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, - PnoScanListener listener) { + @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null"); - int key = addListener(listener); + int key = addListener(listener, executor); if (key == INVALID_KEY) return; validateChannel(); pnoSettings.isConnected = false; diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index d91efbccc2de..3c13562d6952 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -589,4 +589,10 @@ public class BaseWifiService extends IWifiManager.Stub { public int calculateSignalLevel(int rssi) { throw new UnsupportedOperationException(); } + + @Override + public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( + List<ScanResult> scanResults) { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp new file mode 100644 index 000000000000..6a39959e8cfd --- /dev/null +++ b/wifi/tests/Android.bp @@ -0,0 +1,50 @@ +// 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. + +// Make test APK +// ============================================================ + +android_test { + name: "FrameworksWifiApiTests", + + defaults: ["framework-wifi-test-defaults"], + + srcs: ["**/*.java"], + + jacoco: { + include_filter: ["android.net.wifi.*"], + // TODO(b/147521214) need to exclude test classes + exclude_filter: [], + }, + + static_libs: [ + "androidx.test.rules", + "core-test-rules", + "guava", + "mockito-target-minus-junit4", + "net-tests-utils", + "frameworks-base-testutils", + "truth-prebuilt", + ], + + libs: [ + "android.test.runner", + "android.test.base", + ], + + test_suites: [ + "device-tests", + "mts", + ], +} diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk deleted file mode 100644 index d2c385b46eb1..000000000000 --- a/wifi/tests/Android.mk +++ /dev/null @@ -1,66 +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. - -LOCAL_PATH:= $(call my-dir) - -# Make test APK -# ============================================================ -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -# This list is generated from the java source files in this module -# The list is a comma separated list of class names with * matching zero or more characters. -# Example: -# Input files: src/com/android/server/wifi/Test.java src/com/android/server/wifi/AnotherTest.java -# Generated exclude list: com.android.server.wifi.Test*,com.android.server.wifi.AnotherTest* - -# Filter all src files to just java files -local_java_files := $(filter %.java,$(LOCAL_SRC_FILES)) -# Transform java file names into full class names. -# This only works if the class name matches the file name and the directory structure -# matches the package. -local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files))) -# Convert class name list to jacoco exclude list -# This appends a * to all classes and replace the space separators with commas. -# These patterns will match all classes in this module and their inner classes. -jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes))) - -jacoco_include := android.net.wifi.* - -LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include) -LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude) - -LOCAL_STATIC_JAVA_LIBRARIES := \ - androidx.test.rules \ - core-test-rules \ - guava \ - mockito-target-minus-junit4 \ - net-tests-utils \ - frameworks-base-testutils \ - truth-prebuilt \ - -LOCAL_JAVA_LIBRARIES := \ - android.test.runner \ - android.test.base \ - -LOCAL_PACKAGE_NAME := FrameworksWifiApiTests -LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_COMPATIBILITY_SUITE := \ - device-tests \ - mts \ - -include $(BUILD_PACKAGE) diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index eeea7e2a6cd8..6884a4ede27a 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; import java.util.Random; @SmallTest @@ -112,12 +114,18 @@ public class SoftApConfigurationTest { @Test public void testWpa2WithAllFieldCustomized() { + List<MacAddress> testBlockedClientList = new ArrayList<>(); + List<MacAddress> testAllowedClientList = new ArrayList<>(); + testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66")); + testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff")); SoftApConfiguration original = new SoftApConfiguration.Builder() .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .setMaxNumberOfClients(10) .setShutdownTimeoutMillis(500000) + .enableClientControlByUser(true) + .setClientList(testBlockedClientList, testAllowedClientList) .build(); assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( @@ -127,6 +135,9 @@ public class SoftApConfigurationTest { assertThat(original.isHiddenSsid()).isEqualTo(true); assertThat(original.getMaxNumberOfClients()).isEqualTo(10); assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000); + assertThat(original.isClientControlByUserEnabled()).isEqualTo(true); + assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList); + assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList); SoftApConfiguration unparceled = parcelUnparcel(original); assertThat(unparceled).isNotSameAs(original); @@ -238,4 +249,17 @@ public class SoftApConfigurationTest { .setShutdownTimeoutMillis(-1) .build(); } + + @Test(expected = IllegalArgumentException.class) + public void testsetClientListExceptionWhenExistMacAddressInBothList() { + final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77"); + final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff"); + ArrayList<MacAddress> testAllowedClientList = new ArrayList<>(); + testAllowedClientList.add(testMacAddress_1); + testAllowedClientList.add(testMacAddress_2); + ArrayList<MacAddress> testBlockedClientList = new ArrayList<>(); + testBlockedClientList.add(testMacAddress_1); + SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); + configBuilder.setClientList(testBlockedClientList, testAllowedClientList); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 5ac50a0d1c97..5bdc34402cc4 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -70,6 +70,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.net.DhcpInfo; +import android.net.MacAddress; import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; import android.net.wifi.WifiManager.LocalOnlyHotspotObserver; import android.net.wifi.WifiManager.LocalOnlyHotspotReservation; @@ -897,6 +898,25 @@ public class WifiManagerTest { } /* + * Verify client-provided callback is being called through callback proxy + */ + @Test + public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception { + WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77")); + ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor = + ArgumentCaptor.forClass(ISoftApCallback.Stub.class); + mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback); + verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(), + anyInt()); + + callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient, + WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS); + mLooper.dispatchAll(); + verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient, + WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS); + } + + /* * Verify client-provided callback is being called through callback proxy on multiple events */ @Test @@ -2185,4 +2205,18 @@ public class WifiManagerTest { result = WifiManager.parseDppChannelList(channelList); assertEquals(result.size(), 0); } + + /** + * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults. + */ + @Test + public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception { + List<WifiConfiguration> testResults = new ArrayList<>(); + testResults.add(new WifiConfiguration()); + + when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class))) + .thenReturn(testResults); + assertEquals(testResults, mWifiManager + .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>())); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index 4cdc4bc2ad48..ac915447f3c4 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -76,7 +76,7 @@ public class WifiNetworkSuggestionTest { .setSsid(TEST_SSID) .setWpa2Passphrase(TEST_PRESHARED_KEY) .setIsAppInteractionRequired(true) - .setIsUserAllowedToManuallyConnect(false) + .setCredentialSharedWithUser(false) .setPriority(0) .build(); @@ -151,7 +151,7 @@ public class WifiNetworkSuggestionTest { WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder() .setSsid(TEST_SSID) .setWpa3Passphrase(TEST_PRESHARED_KEY) - .setIsUserAllowedToManuallyConnect(true) + .setCredentialSharedWithUser(true) .build(); assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID); @@ -709,14 +709,14 @@ public class WifiNetworkSuggestionTest { /** * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception - * when {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} to + * when {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to * true on a open network suggestion. */ @Test(expected = IllegalStateException.class) public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() { WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder() .setSsid(TEST_SSID) - .setIsUserAllowedToManuallyConnect(true) + .setCredentialSharedWithUser(true) .build(); } } diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java index 1af0bcbf3f30..0cc76b68a15e 100644 --- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java @@ -366,7 +366,7 @@ public class WifiScannerTest { /** * Test behavior of {@link WifiScanner#startDisconnectedPnoScan(ScanSettings, PnoSettings, - * WifiScanner.PnoScanListener)} + * Executor, WifiScanner.PnoScanListener)} * @throws Exception */ @Test @@ -375,7 +375,8 @@ public class WifiScannerTest { PnoSettings pnoSettings = new PnoSettings(); WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class); - mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener); + mWifiScanner.startDisconnectedPnoScan( + scanSettings, pnoSettings, mock(Executor.class), pnoScanListener); mLooper.dispatchAll(); ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); @@ -396,7 +397,7 @@ public class WifiScannerTest { /** * Test behavior of {@link WifiScanner#startConnectedPnoScan(ScanSettings, PnoSettings, - * WifiScanner.PnoScanListener)} + * Executor, WifiScanner.PnoScanListener)} * @throws Exception */ @Test @@ -405,7 +406,8 @@ public class WifiScannerTest { PnoSettings pnoSettings = new PnoSettings(); WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class); - mWifiScanner.startConnectedPnoScan(scanSettings, pnoSettings, pnoScanListener); + mWifiScanner.startConnectedPnoScan( + scanSettings, pnoSettings, mock(Executor.class), pnoScanListener); mLooper.dispatchAll(); ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); @@ -426,7 +428,7 @@ public class WifiScannerTest { /** * Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)} - * WifiScanner.PnoScanListener)} + * Executor, WifiScanner.PnoScanListener)} * @throws Exception */ @Test @@ -435,7 +437,8 @@ public class WifiScannerTest { PnoSettings pnoSettings = new PnoSettings(); WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class); - mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener); + mWifiScanner.startDisconnectedPnoScan( + scanSettings, pnoSettings, mock(Executor.class), pnoScanListener); mLooper.dispatchAll(); mWifiScanner.stopPnoScan(pnoScanListener); mLooper.dispatchAll(); |