diff options
359 files changed, 7310 insertions, 2509 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 16907b3432ec..b753aab33dcd 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -19,6 +19,7 @@ java_defaults { // Add java_aconfig_libraries to here to add them to the core framework srcs: [ ":com.android.hardware.camera2-aconfig-java{.generated_srcjars}", + ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", ], } @@ -30,6 +31,7 @@ java_defaults { libs: ["fake_device_config"], } +// Camera aconfig_declarations { name: "com.android.hardware.camera2-aconfig", package: "com.android.hardware.camera2", @@ -41,3 +43,16 @@ java_aconfig_library { aconfig_declarations: "com.android.hardware.camera2-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Window +aconfig_declarations { + name: "com.android.window.flags.window-aconfig", + package: "com.android.window.flags", + srcs: ["core/java/android/window/flags/*.aconfig"], +} + +java_aconfig_library { + name: "com.android.window.flags.window-aconfig-java", + aconfig_declarations: "com.android.window.flags.window-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 53554bce43b8..431f0b9317ac 100644 --- a/Android.bp +++ b/Android.bp @@ -408,21 +408,18 @@ java_defaults { ], } -java_library { - name: "framework-minus-apex", +// Separated so framework-minus-apex-defaults can be used without the libs dependency +java_defaults { + name: "framework-minus-apex-with-libs-defaults", defaults: ["framework-minus-apex-defaults"], - installable: true, - // For backwards compatibility. - stem: "framework", - apex_available: ["//apex_available:platform"], - visibility: [ - "//frameworks/base", - // TODO(b/147128803) remove the below lines - "//frameworks/base/apex/blobstore/framework", - "//frameworks/base/apex/jobscheduler/framework", - "//frameworks/base/packages/Tethering/tests/unit", - "//packages/modules/Connectivity/Tethering/tests/unit", + libs: [ + "framework-virtualization.stubs.module_lib", + "framework-location.impl", ], +} + +java_defaults { + name: "framework-non-updatable-lint-defaults", lint: { extra_check_modules: ["AndroidFrameworkLintChecker"], disabled_checks: ["ApiMightLeakAppVisibility"], @@ -436,6 +433,43 @@ java_library { "UseOfCallerAwareMethodsWithClearedIdentity", ], }, +} + +// we are unfortunately building the turbine jar twice, but more efficient and less complex +// than generating a similar set of stubs with metalava +java_library { + name: "framework-minus-apex-headers", + defaults: ["framework-minus-apex-defaults"], + installable: false, + // For backwards compatibility. + stem: "framework", + apex_available: ["//apex_available:platform"], + visibility: [ + "//frameworks/base/location", + ], + compile_dex: false, + headers_only: true, +} + +java_library { + name: "framework-minus-apex", + defaults: [ + "framework-minus-apex-with-libs-defaults", + "framework-non-updatable-lint-defaults", + ], + installable: true, + // For backwards compatibility. + stem: "framework", + apex_available: ["//apex_available:platform"], + visibility: [ + "//frameworks/base", + "//frameworks/base/location", + // TODO(b/147128803) remove the below lines + "//frameworks/base/apex/blobstore/framework", + "//frameworks/base/apex/jobscheduler/framework", + "//frameworks/base/packages/Tethering/tests/unit", + "//packages/modules/Connectivity/Tethering/tests/unit", + ], errorprone: { javacflags: [ "-Xep:AndroidFrameworkCompatChange:ERROR", @@ -446,7 +480,7 @@ java_library { java_library { name: "framework-minus-apex-intdefs", - defaults: ["framework-minus-apex-defaults"], + defaults: ["framework-minus-apex-with-libs-defaults"], plugins: ["intdef-annotation-processor"], // Errorprone and android lint will already run on framework-minus-apex, don't rerun them on @@ -474,6 +508,7 @@ java_library { installable: false, // this lib is a build-only library static_libs: [ "app-compat-annotations", + "framework-location.impl", "framework-minus-apex", "framework-updatable-stubs-module_libs_api", ], @@ -701,6 +736,97 @@ stubs_defaults { ], } +// Defaults for the java_sdk_libraries of unbundled jars from framework. +// java_sdk_libraries using these defaults should also add themselves to the +// non_updatable_modules list in frameworks/base/api/api.go +java_defaults { + name: "framework-non-updatable-unbundled-defaults", + defaults: ["framework-non-updatable-lint-defaults"], + + sdk_version: "core_platform", + + // Api scope settings + public: { + enabled: true, + sdk_version: "module_current", + libs: ["android_module_lib_stubs_current"], + }, + system: { + enabled: true, + sdk_version: "module_current", + libs: ["android_module_lib_stubs_current"], + }, + module_lib: { + enabled: true, + sdk_version: "module_current", + libs: ["android_module_lib_stubs_current"], + }, + test: { + enabled: true, + sdk_version: "test_frameworks_core_current", + libs: ["android_test_frameworks_core_stubs_current"], + }, + + stub_only_libs: [ + "framework-protos", + ], + impl_only_libs: [ + "framework-minus-apex-headers", // full access to framework-minus-apex including hidden API + "framework-annotations-lib", + ], + visibility: ["//visibility:public"], + stubs_library_visibility: ["//visibility:public"], + stubs_source_visibility: ["//visibility:private"], + impl_library_visibility: [ + ":__pkg__", + "//frameworks/base", + "//frameworks/base/api", // For framework-all + ], + defaults_visibility: [ + "//frameworks/base/location", + ], + plugins: [ + "error_prone_android_framework", + ], + errorprone: { + javacflags: [ + "-Xep:AndroidFrameworkCompatChange:ERROR", + "-Xep:AndroidFrameworkUid:ERROR", + ], + }, + + // Include manual annotations in API txt files + merge_annotations_dirs: ["metalava-manual"], + + // Use the source of annotations that affect metalava doc generation, since + // the relevant generation instructions are themselves in javadoc, which is + // not present in class files. + api_srcs: [":framework-metalava-annotations"], + + // Framework modules are not generally shared libraries, i.e. they are not + // intended, and must not be allowed, to be used in a <uses-library> manifest + // entry. + shared_library: false, + + // Prevent dependencies that do not specify an sdk_version from accessing the + // implementation library by default and force them to use stubs instead. + default_to_stubs: true, + + // Subdirectory for the artifacts that are copied to the dist directory + dist_group: "android", + + droiddoc_options: [ + "--error UnhiddenSystemApi " + + "--hide CallbackInterface " + + "--hide HiddenTypedefConstant " + + "--hide RequiresPermission " + + "--enhance-documentation " + + "--hide-package com.android.server ", + ], + + annotations_enabled: true, +} + build = [ "AconfigFlags.bp", "ProtoLibraries.bp", diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java index a5d1e00139b5..59f08f645def 100644 --- a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java +++ b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java @@ -140,7 +140,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase { state.resumeTiming(); } - // Sanity check + // Check for no errors callback.assertNoAsyncErrors(); } finally { mAfm.unregisterCallback(callback); @@ -190,7 +190,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase { state.resumeTiming(); } - // Sanity check + // Check for no errors callback.assertNoAsyncErrors(); } finally { mAfm.unregisterCallback(callback); @@ -303,7 +303,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase { callback.expectEvent(mPassword, EVENT_INPUT_SHOWN); } - // Sanity check + // Check for no errors callback.assertNoAsyncErrors(); } finally { mAfm.unregisterCallback(callback); diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt index 9482591c65b5..aadbc2319a62 100644 --- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt +++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt @@ -130,7 +130,7 @@ class MotionPredictorBenchmark { eventTime, ACTION_MOVE, /*x=*/eventPosition, /*y=*/eventPosition) predictor.record(moveEvent) val predictionTime = eventTime + eventInterval - val predicted = predictor.predict(predictionTime.toNanos()) + val predicted = checkNotNull(predictor.predict(predictionTime.toNanos())) assertTrue(predicted.eventTime <= (predictionTime + offset).toMillis()) } } diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index da700aaca047..68717623d05d 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -104,16 +104,16 @@ public class UserLifecycleTests { /** Name of users/profiles in the test. Users with this name may be freely removed. */ private static final String TEST_USER_NAME = "UserLifecycleTests_test_user"; - /** Name of dummy package used when timing how long app launches take. */ + /** Name of placeholder package used when timing how long app launches take. */ private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp"; - // Copy of UserSystemPackageInstaller whitelist mode constants. - private static final String PACKAGE_WHITELIST_MODE_PROP = + // Copy of UserSystemPackageInstaller allowlist mode constants. + private static final String PACKAGE_ALLOWLIST_MODE_PROP = "persist.debug.user.package_whitelist_mode"; - private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0; - private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001; - private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100; - private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1; + private static final int USER_TYPE_PACKAGE_ALLOWLIST_MODE_DISABLE = 0; + private static final int USER_TYPE_PACKAGE_ALLOWLIST_MODE_ENFORCE = 0b001; + private static final int USER_TYPE_PACKAGE_ALLOWLIST_MODE_IMPLICIT_ALLOWLIST = 0b100; + private static final int USER_TYPE_PACKAGE_ALLOWLIST_MODE_DEVICE_DEFAULT = -1; private UserManager mUm; private ActivityManager mAm; @@ -1178,13 +1178,13 @@ public class UserLifecycleTests { } // TODO: This is just a POC. Do this properly and add more. - /** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */ + /** Tests starting (unlocking) a newly-created profile using the user-type-pkg-allowlist. */ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS) public void managedProfileUnlock_usingWhitelist() throws RemoteException { assumeTrue(mHasManagedUserFeature); - final int origMode = getUserTypePackageWhitelistMode(); - setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE - | USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST); + final int origMode = getUserTypePackageAllowlistMode(); + setUserTypePackageAllowlistMode(USER_TYPE_PACKAGE_ALLOWLIST_MODE_ENFORCE + | USER_TYPE_PACKAGE_ALLOWLIST_MODE_IMPLICIT_ALLOWLIST); try { while (mRunner.keepRunning()) { @@ -1201,15 +1201,15 @@ public class UserLifecycleTests { mRunner.resumeTimingForNextIteration(); } } finally { - setUserTypePackageWhitelistMode(origMode); + setUserTypePackageAllowlistMode(origMode); } } - /** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */ + /** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-allowlist. */ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS) public void managedProfileUnlock_notUsingWhitelist() throws RemoteException { assumeTrue(mHasManagedUserFeature); - final int origMode = getUserTypePackageWhitelistMode(); - setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE); + final int origMode = getUserTypePackageAllowlistMode(); + setUserTypePackageAllowlistMode(USER_TYPE_PACKAGE_ALLOWLIST_MODE_DISABLE); try { while (mRunner.keepRunning()) { @@ -1226,7 +1226,7 @@ public class UserLifecycleTests { mRunner.resumeTimingForNextIteration(); } } finally { - setUserTypePackageWhitelistMode(origMode); + setUserTypePackageAllowlistMode(origMode); } } @@ -1456,17 +1456,17 @@ public class UserLifecycleTests { attestTrue(errMsg, success); } - /** Gets the PACKAGE_WHITELIST_MODE_PROP System Property. */ - private int getUserTypePackageWhitelistMode() { - return SystemProperties.getInt(PACKAGE_WHITELIST_MODE_PROP, - USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT); + /** Gets the PACKAGE_ALLOWLIST_MODE_PROP System Property. */ + private int getUserTypePackageAllowlistMode() { + return SystemProperties.getInt(PACKAGE_ALLOWLIST_MODE_PROP, + USER_TYPE_PACKAGE_ALLOWLIST_MODE_DEVICE_DEFAULT); } - /** Sets the PACKAGE_WHITELIST_MODE_PROP System Property to the given value. */ - private void setUserTypePackageWhitelistMode(int mode) { + /** Sets the PACKAGE_ALLOWLIST_MODE_PROP System Property to the given value. */ + private void setUserTypePackageAllowlistMode(int mode) { String result = ShellHelper.runShellCommand( - String.format("setprop %s %d", PACKAGE_WHITELIST_MODE_PROP, mode)); - attestFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result, + String.format("setprop %s %d", PACKAGE_ALLOWLIST_MODE_PROP, mode)); + attestFalse("Failed to set sysprop " + PACKAGE_ALLOWLIST_MODE_PROP + ": " + result, result != null && result.contains("Failed")); } diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index b0da7d1e2870..c42c7ca25133 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -111,7 +111,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase stopProfiling(); } - /** A dummy view to get IWindow. */ + /** A placeholder view to get IWindow. */ private static class ContentView extends LinearLayout { ContentView(Context context) { super(context); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 109686d76b2f..e636f604ddb0 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -456,6 +456,7 @@ public final class JobServiceContext implements ServiceConnection { if (DEBUG) { Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable."); } + mContext.unbindService(this); mRunningJob = null; mRunningJobWorkType = WORK_TYPE_NONE; mRunningCallback = null; diff --git a/api/Android.bp b/api/Android.bp index e9cc40513221..6986ac09f89e 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -87,6 +87,7 @@ combined_apis { "framework-devicelock", "framework-graphics", "framework-healthfitness", + "framework-location", "framework-media", "framework-mediaprovider", "framework-ondevicepersonalization", diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index c37ff9761757..e481fe9971fc 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -487,7 +487,6 @@ java_library { static_libs: [ "all-updatable-modules-system-stubs", "android-non-updatable.stubs.test", - "private-stub-annotations-jar", ], defaults: [ "android.jar_defaults", @@ -613,6 +612,7 @@ java_defaults { api_contributions: [ "test-api-stubs-docs-non-updatable.api.contribution", "framework-virtualization.stubs.source.test.api.contribution", + "framework-location.stubs.source.test.api.contribution", ], } diff --git a/api/api.go b/api/api.go index d5c6145ba062..6095a9a781d8 100644 --- a/api/api.go +++ b/api/api.go @@ -31,6 +31,7 @@ const art = "art.module.public.api" const conscrypt = "conscrypt.module.public.api" const i18n = "i18n.module.public.api" const virtualization = "framework-virtualization" +const location = "framework-location" var core_libraries_modules = []string{art, conscrypt, i18n} @@ -42,7 +43,7 @@ var core_libraries_modules = []string{art, conscrypt, i18n} // APIs. // In addition, the modules in this list are allowed to contribute to test APIs // stubs. -var non_updatable_modules = []string{virtualization} +var non_updatable_modules = []string{virtualization, location} // The intention behind this soong plugin is to generate a number of "merged" // API-related modules that would otherwise require a large amount of very @@ -278,8 +279,10 @@ func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) { } func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) { - // The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes. + // The user of this module compiles against the "core" SDK and against non-updatable modules, + // so remove to avoid dupes. modules = removeAll(modules, core_libraries_modules) + modules = removeAll(modules, non_updatable_modules) props := libraryProps{} props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api") props.Static_libs = transformArray(modules, "", ".stubs.module_lib") diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp index 3534624a58a2..98767ee733ad 100644 --- a/cmds/bootanimation/Android.bp +++ b/cmds/bootanimation/Android.bp @@ -74,4 +74,7 @@ cc_library_shared { "libGLESv2", "libgui", ], + whole_static_libs: [ + "libc++fs", + ], } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 774ba7460c6b..b56dceb9393f 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "BootAnimation" #define ATRACE_TAG ATRACE_TAG_GRAPHICS +#include <filesystem> #include <vector> #include <stdint.h> @@ -1306,10 +1307,10 @@ bool BootAnimation::preloadZip(Animation& animation) { continue; } - const String8 entryName(name); - const String8 path(entryName.getPathDir()); - const String8 leaf(entryName.getPathLeaf()); - if (leaf.size() > 0) { + const std::filesystem::path entryName(name); + const std::filesystem::path path(entryName.parent_path()); + const std::filesystem::path leaf(entryName.filename()); + if (!leaf.empty()) { if (entryName == CLOCK_FONT_ZIP_NAME) { FileMap* map = zip->createEntryFileMap(entry); if (map) { @@ -1327,7 +1328,7 @@ bool BootAnimation::preloadZip(Animation& animation) { } for (size_t j = 0; j < pcount; j++) { - if (path == animation.parts[j].path) { + if (path.string() == animation.parts[j].path.c_str()) { uint16_t method; // supports only stored png files if (zip->getEntryInfo(entry, &method, nullptr, nullptr, nullptr, nullptr, nullptr)) { @@ -1344,7 +1345,7 @@ bool BootAnimation::preloadZip(Animation& animation) { map->getDataLength()); } else { Animation::Frame frame; - frame.name = leaf; + frame.name = leaf.c_str(); frame.map = map; frame.trimWidth = animation.width; frame.trimHeight = animation.height; diff --git a/core/api/current.txt b/core/api/current.txt index cb929261bde1..06bd72dc8898 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -47549,7 +47549,7 @@ package android.text { method @IntRange(from=0) public final int getWidth(); method public final void increaseWidthTo(int); method public boolean isFallbackLineSpacingEnabled(); - method public final boolean isIncludeFontPadding(); + method public final boolean isFontPaddingIncluded(); method public boolean isRtlCharAt(int); method protected final boolean isSpanned(); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 @@ -47585,13 +47585,13 @@ package android.text { method @NonNull public android.text.Layout.Builder setEllipsize(@Nullable android.text.TextUtils.TruncateAt); method @NonNull public android.text.Layout.Builder setEllipsizedWidth(@IntRange(from=0) int); method @NonNull public android.text.Layout.Builder setFallbackLineSpacingEnabled(boolean); + method @NonNull public android.text.Layout.Builder setFontPaddingIncluded(boolean); method @NonNull public android.text.Layout.Builder setHyphenationFrequency(int); - method @NonNull public android.text.Layout.Builder setIncludeFontPadding(boolean); method @NonNull public android.text.Layout.Builder setJustificationMode(int); method @NonNull public android.text.Layout.Builder setLeftIndents(@Nullable int[]); method @NonNull public android.text.Layout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method @NonNull public android.text.Layout.Builder setLineSpacingAmount(float); - method @NonNull public android.text.Layout.Builder setLineSpacingMultiplier(float); + method @NonNull public android.text.Layout.Builder setLineSpacingMultiplier(@FloatRange(from=0) float); method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int); method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]); method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 61415839ea2c..2b3e8e9e0a92 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -301,6 +301,7 @@ package android { field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE"; field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER"; field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER"; + field public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE"; field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION"; field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM"; field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; @@ -17233,9 +17234,8 @@ package android.telephony.satellite { method public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); } - public class SatelliteManager { + public final class SatelliteManager { method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void onDeviceAlignedWithSatellite(boolean); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); @@ -17250,6 +17250,7 @@ package android.telephony.satellite { method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 3927b40fdc74..3abe1d9494f0 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -24,6 +24,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UiThread; import android.annotation.UserIdInt; import android.app.IServiceConnection; import android.app.PendingIntent; @@ -39,6 +40,10 @@ import android.content.pm.ShortcutInfo; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.DisplayMetrics; @@ -53,6 +58,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; /** * Updates AppWidget state; gets information about installed AppWidget providers and other @@ -475,6 +481,8 @@ public class AppWidgetManager { private static final String TAG = "AppWidgetManager"; + private static Executor sUpdateExecutor; + /** * An intent extra that contains multiple appWidgetIds. These are id values as * they were provided to the application during a recent restore from backup. It is @@ -510,6 +518,8 @@ public class AppWidgetManager { private final IAppWidgetService mService; private final DisplayMetrics mDisplayMetrics; + private boolean mHasPostedLegacyLists = false; + /** * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context * Context} object. @@ -552,6 +562,13 @@ public class AppWidgetManager { }); } + private boolean isPostingTaskToBackground(@Nullable RemoteViews views) { + return Looper.myLooper() == Looper.getMainLooper() + && RemoteViews.isAdapterConversionEnabled() + && (mHasPostedLegacyLists = mHasPostedLegacyLists + || (views != null && views.hasLegacyLists())); + } + /** * Set the RemoteViews to use for the specified appWidgetIds. * <p> @@ -575,6 +592,19 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (isPostingTaskToBackground(views)) { + createUpdateExecutorIfNull().execute(() -> { + try { + mService.updateAppWidgetIds(mPackageName, appWidgetIds, views); + } catch (RemoteException e) { + Log.e(TAG, "Error updating app widget views in background", e); + } + }); + + return; + } + try { mService.updateAppWidgetIds(mPackageName, appWidgetIds, views); } catch (RemoteException e) { @@ -683,6 +713,19 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (isPostingTaskToBackground(views)) { + createUpdateExecutorIfNull().execute(() -> { + try { + mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views); + } catch (RemoteException e) { + Log.e(TAG, "Error partially updating app widget views in background", e); + } + }); + + return; + } + try { mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views); } catch (RemoteException e) { @@ -738,6 +781,19 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (isPostingTaskToBackground(views)) { + createUpdateExecutorIfNull().execute(() -> { + try { + mService.updateAppWidgetProvider(provider, views); + } catch (RemoteException e) { + Log.e(TAG, "Error updating app widget view using provider in background", e); + } + }); + + return; + } + try { mService.updateAppWidgetProvider(provider, views); } catch (RemoteException e) { @@ -797,28 +853,45 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (!RemoteViews.isAdapterConversionEnabled()) { + try { + mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + + return; + } + + if (Looper.myLooper() == Looper.getMainLooper()) { + mHasPostedLegacyLists = true; + createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds, + viewId)); + } else { + notifyCollectionWidgetChange(appWidgetIds, viewId); + } + } + + private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) { try { - if (RemoteViews.isAdapterConversionEnabled()) { - List<CompletableFuture<Void>> updateFutures = new ArrayList<>(); - for (int i = 0; i < appWidgetIds.length; i++) { - final int widgetId = appWidgetIds[i]; - updateFutures.add(CompletableFuture.runAsync(() -> { - try { - RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); - if (views.replaceRemoteCollections(viewId)) { - updateAppWidget(widgetId, views); - } - } catch (Exception e) { - Log.e(TAG, "Error notifying changes in RemoteViews", e); + List<CompletableFuture<Void>> updateFutures = new ArrayList<>(); + for (int i = 0; i < appWidgetIds.length; i++) { + final int widgetId = appWidgetIds[i]; + updateFutures.add(CompletableFuture.runAsync(() -> { + try { + RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); + if (views.replaceRemoteCollections(viewId)) { + updateAppWidget(widgetId, views); } - })); - } - CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join(); - } else { - mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId); + } catch (Exception e) { + Log.e(TAG, "Error notifying changes in RemoteViews", e); + } + })); } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join(); + } catch (Exception e) { + Log.e(TAG, "Error notifying changes for all widgets", e); } } @@ -1360,4 +1433,20 @@ public class AppWidgetManager { throw e.rethrowFromSystemServer(); } } + + @UiThread + private static @NonNull Executor createUpdateExecutorIfNull() { + if (sUpdateExecutor == null) { + sUpdateExecutor = new HandlerExecutor(createAndStartNewHandler( + "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND)); + } + + return sUpdateExecutor; + } + + private static @NonNull Handler createAndStartNewHandler(@NonNull String name, int priority) { + HandlerThread thread = new HandlerThread(name, priority); + thread.start(); + return thread.getThreadHandler(); + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5ef7b117d86e..d6dee9389c8e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -593,7 +593,7 @@ public abstract class Context { public static final int BIND_IMPORTANT_BACKGROUND = 0x00800000; /** - * @hide Flag for {@link #bindService}: allows application hosting service to manage whitelists + * @hide Flag for {@link #bindService}: allows application hosting service to manage allowlists * such as temporary allowing a {@code PendingIntent} to bypass Power Save mode. */ public static final int BIND_ALLOW_WHITELIST_MANAGEMENT = 0x01000000; diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index 3686898575aa..b074e8b0c31b 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -396,9 +396,9 @@ public class IntentSender implements Parcelable { } /** @hide */ - public IntentSender(IIntentSender target, IBinder whitelistToken) { + public IntentSender(IIntentSender target, IBinder allowlistToken) { mTarget = target; - mWhitelistToken = whitelistToken; + mWhitelistToken = allowlistToken; } /** @hide */ diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 8cb96ba09748..f417480e9e1a 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2929,7 +2929,13 @@ public class PackageInstaller { * * By default this is the app that created the {@link PackageInstaller} object. * - * @param installerPackageName name of the installer package + * Note: Only applications with {@link android.Manifest.permission#INSTALL_PACKAGES} + * permission are allowed to set an installer that is not the caller's own installer + * package name, otherwise it will cause a {@link SecurityException} when creating the + * install session. + * + * @param installerPackageName The name of the installer package, its length must be less + * than {@code 255}, otherwise it will be invalid. */ public void setInstallerPackageName(@Nullable String installerPackageName) { this.installerPackageName = installerPackageName; diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index ccc39b6080d7..039644387715 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -41,11 +41,6 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RSIllegalArgumentException; -import android.renderscript.RenderScript; -import android.renderscript.Type; import android.text.TextUtils; import android.util.Log; import android.view.Surface; @@ -1007,132 +1002,6 @@ public class Camera { private native final void _addCallbackBuffer( byte[] callbackBuffer, int msgType); - /** - * <p>Create a {@link android.renderscript RenderScript} - * {@link android.renderscript.Allocation Allocation} to use as a - * destination of preview callback frames. Use - * {@link #setPreviewCallbackAllocation setPreviewCallbackAllocation} to use - * the created Allocation as a destination for camera preview frames.</p> - * - * <p>The Allocation will be created with a YUV type, and its contents must - * be accessed within Renderscript with the {@code rsGetElementAtYuv_*} - * accessor methods. Its size will be based on the current - * {@link Parameters#getPreviewSize preview size} configured for this - * camera.</p> - * - * @param rs the RenderScript context for this Allocation. - * @param usage additional usage flags to set for the Allocation. The usage - * flag {@link android.renderscript.Allocation#USAGE_IO_INPUT} will always - * be set on the created Allocation, but additional flags may be provided - * here. - * @return a new YUV-type Allocation with dimensions equal to the current - * preview size. - * @throws RSIllegalArgumentException if the usage flags are not compatible - * with an YUV Allocation. - * @see #setPreviewCallbackAllocation - * @hide - */ - public final Allocation createPreviewAllocation(RenderScript rs, int usage) - throws RSIllegalArgumentException { - Parameters p = getParameters(); - Size previewSize = p.getPreviewSize(); - Type.Builder yuvBuilder = new Type.Builder(rs, - Element.createPixel(rs, - Element.DataType.UNSIGNED_8, - Element.DataKind.PIXEL_YUV)); - // Use YV12 for wide compatibility. Changing this requires also - // adjusting camera service's format selection. - yuvBuilder.setYuvFormat(ImageFormat.YV12); - yuvBuilder.setX(previewSize.width); - yuvBuilder.setY(previewSize.height); - - Allocation a = Allocation.createTyped(rs, yuvBuilder.create(), - usage | Allocation.USAGE_IO_INPUT); - - return a; - } - - /** - * <p>Set an {@link android.renderscript.Allocation Allocation} as the - * target of preview callback data. Use this method for efficient processing - * of camera preview data with RenderScript. The Allocation must be created - * with the {@link #createPreviewAllocation createPreviewAllocation } - * method.</p> - * - * <p>Setting a preview allocation will disable any active preview callbacks - * set by {@link #setPreviewCallback setPreviewCallback} or - * {@link #setPreviewCallbackWithBuffer setPreviewCallbackWithBuffer}, and - * vice versa. Using a preview allocation still requires an active standard - * preview target to be set, either with - * {@link #setPreviewTexture setPreviewTexture} or - * {@link #setPreviewDisplay setPreviewDisplay}.</p> - * - * <p>To be notified when new frames are available to the Allocation, use - * {@link android.renderscript.Allocation#setIoInputNotificationHandler Allocation.setIoInputNotificationHandler}. To - * update the frame currently accessible from the Allocation to the latest - * preview frame, call - * {@link android.renderscript.Allocation#ioReceive Allocation.ioReceive}.</p> - * - * <p>To disable preview into the Allocation, call this method with a - * {@code null} parameter.</p> - * - * <p>Once a preview allocation is set, the preview size set by - * {@link Parameters#setPreviewSize setPreviewSize} cannot be changed. If - * you wish to change the preview size, first remove the preview allocation - * by calling {@code setPreviewCallbackAllocation(null)}, then change the - * preview size, create a new preview Allocation with - * {@link #createPreviewAllocation createPreviewAllocation}, and set it as - * the new preview callback allocation target.</p> - * - * <p>If you are using the preview data to create video or still images, - * strongly consider using {@link android.media.MediaActionSound} to - * properly indicate image capture or recording start/stop to the user.</p> - * - * @param previewAllocation the allocation to use as destination for preview - * @throws IOException if configuring the camera to use the Allocation for - * preview fails. - * @throws IllegalArgumentException if the Allocation's dimensions or other - * parameters don't meet the requirements. - * @see #createPreviewAllocation - * @see #setPreviewCallback - * @see #setPreviewCallbackWithBuffer - * @hide - */ - public final void setPreviewCallbackAllocation(Allocation previewAllocation) - throws IOException { - Surface previewSurface = null; - if (previewAllocation != null) { - Parameters p = getParameters(); - Size previewSize = p.getPreviewSize(); - if (previewSize.width != previewAllocation.getType().getX() || - previewSize.height != previewAllocation.getType().getY()) { - throw new IllegalArgumentException( - "Allocation dimensions don't match preview dimensions: " + - "Allocation is " + - previewAllocation.getType().getX() + - ", " + - previewAllocation.getType().getY() + - ". Preview is " + previewSize.width + ", " + - previewSize.height); - } - if ((previewAllocation.getUsage() & - Allocation.USAGE_IO_INPUT) == 0) { - throw new IllegalArgumentException( - "Allocation usage does not include USAGE_IO_INPUT"); - } - if (previewAllocation.getType().getElement().getDataKind() != - Element.DataKind.PIXEL_YUV) { - throw new IllegalArgumentException( - "Allocation is not of a YUV type"); - } - previewSurface = previewAllocation.getSurface(); - mUsingPreviewAllocation = true; - } else { - mUsingPreviewAllocation = false; - } - setPreviewCallbackSurface(previewSurface); - } - private native final void setPreviewCallbackSurface(Surface s); private class EventHandler extends Handler diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 0e45787c1340..082a3361be4e 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -541,14 +541,6 @@ public abstract class CameraDevice implements AutoCloseable { * or configuring it to use one of the supported * {@link android.media.CamcorderProfile CamcorderProfiles}.</li> * - * <li>For efficient YUV processing with {@link android.renderscript}: - * Create a RenderScript - * {@link android.renderscript.Allocation Allocation} with a supported YUV - * type, the IO_INPUT flag, and one of the sizes returned by - * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(Allocation.class)}, - * Then obtain the Surface with - * {@link android.renderscript.Allocation#getSurface}.</li> - * * <li>For access to RAW, uncompressed YUV, or compressed JPEG data in the application: Create an * {@link android.media.ImageReader} object with one of the supported output formats given by * {@link StreamConfigurationMap#getOutputFormats()}, setting its size to one of the diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index ef0db7f8a41c..b85d6869a1ff 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -509,8 +509,6 @@ public final class StreamConfigurationMap { * Recommended for recording video (simple to use) * <li>{@link android.media.MediaCodec} - * Recommended for recording video (more complicated to use, with more flexibility) - * <li>{@link android.renderscript.Allocation} - - * Recommended for image processing with {@link android.renderscript RenderScript} * <li>{@link android.view.SurfaceHolder} - * Recommended for low-power camera preview with {@link android.view.SurfaceView} * <li>{@link android.graphics.SurfaceTexture} - diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index af09a0662795..5b24dcacbf53 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -1294,32 +1294,30 @@ public final class FileUtils { * Round the given size of a storage device to a nice round power-of-two * value, such as 256MB or 32GB. This avoids showing weird values like * "29.5GB" in UI. - * - * Some storage devices are still using GiB (powers of 1024) over - * GB (powers of 1000) measurements and this method takes it into account. - * * Round ranges: * ... - * [256 GiB + 1; 512 GiB] -> 512 GB - * [512 GiB + 1; 1 TiB] -> 1 TB - * [1 TiB + 1; 2 TiB] -> 2 TB + * (128 GB; 256 GB] -> 256 GB + * (256 GB; 512 GB] -> 512 GB + * (512 GB; 1000 GB] -> 1000 GB + * (1000 GB; 2000 GB] -> 2000 GB + * ... * etc * * @hide */ public static long roundStorageSize(long size) { long val = 1; - long kiloPow = 1; - long kibiPow = 1; - while ((val * kibiPow) < size) { + long pow = 1; + while ((val * pow) < size) { val <<= 1; if (val > 512) { val = 1; - kibiPow *= 1024; - kiloPow *= 1000; + pow *= 1000; } } - return val * kiloPow; + + Log.d(TAG, String.format("Rounded bytes from %d to %d", size, val * pow)); + return val * pow; } private static long toBytes(long value, String unit) { diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index bc52744078ea..369a1932e437 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -174,4 +174,5 @@ interface IStorageManager { boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 95; void setCloudMediaProvider(in String authority) = 96; String getCloudMediaProvider() = 97; + long getInternalStorageBlockDeviceSize() = 98; }
\ No newline at end of file diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 80dd48825ba7..ee387e7c284f 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1359,6 +1359,15 @@ public class StorageManager { } /** {@hide} */ + public long getInternalStorageBlockDeviceSize() { + try { + return mStorageManager.getInternalStorageBlockDeviceSize(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ public void mkdirs(File file) { BlockGuard.getVmPolicy().onPathAccess(file.getAbsolutePath()); try { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2791c412f5c7..b9a658344e7d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4579,6 +4579,16 @@ public final class Settings { public static final String WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE = "wear_accessibility_gesture_enabled_during_oobe"; + + /** + * If the text-to-speech pre-warm is enabled. + * Set to 1 for true and 0 for false. + * + * This setting is used only internally. + * @hide + */ + public static final String WEAR_TTS_PREWARM_ENABLED = "wear_tts_prewarm_enabled"; + /** * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_ON} instead */ @@ -6017,6 +6027,7 @@ public final class Settings { PRIVATE_SETTINGS.add(ADVANCED_SETTINGS); PRIVATE_SETTINGS.add(WEAR_ACCESSIBILITY_GESTURE_ENABLED); PRIVATE_SETTINGS.add(WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE); + PRIVATE_SETTINGS.add(WEAR_TTS_PREWARM_ENABLED); PRIVATE_SETTINGS.add(SCREEN_AUTO_BRIGHTNESS_ADJ); PRIVATE_SETTINGS.add(VIBRATE_INPUT_DEVICES); PRIVATE_SETTINGS.add(VOLUME_MASTER); diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS index 4e8d6e70091c..22b1f026e2ae 100644 --- a/core/java/android/security/OWNERS +++ b/core/java/android/security/OWNERS @@ -1,9 +1,9 @@ # Bug component: 36824 -cbrubaker@google.com +brambonne@google.com +brufino@google.com +jeffv@google.com -per-file NetworkSecurityPolicy.java = cbrubaker@google.com -per-file NetworkSecurityPolicy.java = klyubin@google.com -per-file FrameworkNetworkSecurityPolicy.java = cbrubaker@google.com -per-file FrameworkNetworkSecurityPolicy.java = klyubin@google.com +per-file *NetworkSecurityPolicy.java = file:net/OWNERS per-file Confirmation*.java = file:/keystore/OWNERS +per-file FileIntegrityManager.java = victorhsieh@google.com diff --git a/core/java/android/security/net/OWNERS b/core/java/android/security/net/OWNERS index d8281645738b..1d52eed0cb56 100644 --- a/core/java/android/security/net/OWNERS +++ b/core/java/android/security/net/OWNERS @@ -1,4 +1,4 @@ # Bug component: 36824 -cbrubaker@google.com brambonne@google.com +jeffv@google.com diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 98b9fee34a20..c450dc8d4d92 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -16,6 +16,7 @@ package android.text; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -3407,7 +3408,7 @@ public abstract class Layout { * * The specified amount of pixels will be added to each line. * - * The default value is {@code 0}. + * The default value is {@code 0}. The negative value is allowed for squeezing lines. * * @param amount an amount of pixels to be added to line height. * @return this builder instance. @@ -3435,7 +3436,7 @@ public abstract class Layout { * @see StaticLayout.Builder#setLineSpacing(float, float) */ @NonNull - public Builder setLineSpacingMultiplier(float multiplier) { + public Builder setLineSpacingMultiplier(@FloatRange(from = 0) float multiplier) { mSpacingMult = multiplier; return this; } @@ -3450,11 +3451,11 @@ public abstract class Layout { * * @param includeFontPadding true for including extra space into first and last line. * @return this builder instance. - * @see Layout#isIncludeFontPadding() + * @see Layout#isFontPaddingIncluded() * @see StaticLayout.Builder#setIncludePad(boolean) */ @NonNull - public Builder setIncludeFontPadding(boolean includeFontPadding) { + public Builder setFontPaddingIncluded(boolean includeFontPadding) { mIncludePad = includeFontPadding; return this; } @@ -3853,10 +3854,10 @@ public abstract class Layout { * Returns true if this layout is created with increased line height. * * @return true if the layout is created with increased line height. - * @see Layout.Builder#setIncludeFontPadding(boolean) + * @see Layout.Builder#setFontPaddingIncluded(boolean) * @see StaticLayout.Builder#setIncludePad(boolean) */ - public final boolean isIncludeFontPadding() { + public final boolean isFontPaddingIncluded() { return mIncludePad; } @@ -3979,7 +3980,7 @@ public abstract class Layout { @Nullable public final int[] getRightIndents() { if (mRightIndents == null) { - return mLeftIndents; + return null; } int[] newArray = new int[mRightIndents.length]; System.arraycopy(mRightIndents, 0, newArray, 0, newArray.length); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 827600c83fae..85f5395f2657 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -247,6 +247,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true"); DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false"); + DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false"); } private static final Set<String> PERSISTENT_FLAGS; diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index 511cb2df712d..ac76fc2eb469 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -212,6 +212,11 @@ public class IntArray implements Cloneable { return -1; } + /** Returns {@code true} if this array contains the specified value. */ + public boolean contains(int value) { + return indexOf(value) != -1; + } + /** * Removes the value at the specified index from this array. */ diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index ca33c5e05944..f1458fa11770 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -977,7 +977,7 @@ public final class Choreographer { if (callbackType == Choreographer.CALLBACK_COMMIT) { final long jitterNanos = now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); - if (jitterNanos >= 2 * frameIntervalNanos) { + if (frameIntervalNanos > 0 && jitterNanos >= 2 * frameIntervalNanos) { final long lastFrameOffset = jitterNanos % frameIntervalNanos + frameIntervalNanos; if (DEBUG_JANK) { diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 6e73a3c93fd7..23afb03569ce 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -346,13 +346,13 @@ public class HandwritingInitiator { } private static boolean shouldTriggerStylusHandwritingForView(@NonNull View view) { - if (!view.isAutoHandwritingEnabled()) { + if (!view.shouldInitiateHandwriting()) { return false; } - // The view may be a handwriting initiation delegate, in which case it is not the editor + // The view may be a handwriting initiation delegator, in which case it is not the editor // view for which handwriting would be started. However, in almost all cases, the return - // values of View#isStylusHandwritingAvailable will be the same for the delegate view and - // the delegator editor view. So the delegate view can be used to decide whether handwriting + // values of View#isStylusHandwritingAvailable will be the same for the delegator view and + // the delegate editor view. So the delegator view can be used to decide whether handwriting // should be triggered. return view.isStylusHandwritingAvailable(); } @@ -677,7 +677,7 @@ public class HandwritingInitiator { /** The helper method to check if the given view is still active for handwriting. */ private static boolean isViewActive(@Nullable View view) { return view != null && view.isAttachedToWindow() && view.isAggregatedVisible() - && view.isAutoHandwritingEnabled(); + && view.shouldInitiateHandwriting(); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5e19c675bb88..87036099239d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5488,7 +5488,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) | (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) | (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT); - mPrivateFlags4 = PFLAG4_AUTO_HANDWRITING_ENABLED; final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); @@ -6213,7 +6212,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPreferKeepClear(a.getBoolean(attr, false)); break; case R.styleable.View_autoHandwritingEnabled: - setAutoHandwritingEnabled(a.getBoolean(attr, true)); + setAutoHandwritingEnabled(a.getBoolean(attr, false)); break; case R.styleable.View_handwritingBoundsOffsetLeft: mHandwritingBoundsOffsetLeft = a.getDimension(attr, 0); @@ -12150,7 +12149,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (getSystemGestureExclusionRects().isEmpty() && collectPreferKeepClearRects().isEmpty() && collectUnrestrictedPreferKeepClearRects().isEmpty() - && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) { + && (info.mHandwritingArea == null || !shouldInitiateHandwriting())) { if (info.mPositionUpdateListener != null) { mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener); info.mPositionUpdateListener = null; @@ -12517,7 +12516,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void updateHandwritingArea() { // If autoHandwritingArea is not enabled, do nothing. - if (!isAutoHandwritingEnabled()) return; + if (!shouldInitiateHandwriting()) return; final AttachInfo ai = mAttachInfo; if (ai != null) { ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this); @@ -12525,6 +12524,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns true if a stylus {@link MotionEvent} within this view's bounds should initiate + * handwriting mode, either for this view ({@link #isAutoHandwritingEnabled()} is {@code true}) + * or for a handwriting delegate view ({@link #getHandwritingDelegatorCallback()} is not {@code + * null}). + */ + boolean shouldInitiateHandwriting() { + return isAutoHandwritingEnabled() || getHandwritingDelegatorCallback() != null; + } + + /** * Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this * view's bounds. The callback will be called from the UI thread. * diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ddd3269925a4..e0fda7e51c33 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4127,7 +4127,7 @@ public final class ViewRootImpl implements ViewParent, } private void fireAccessibilityFocusEventIfHasFocusedNode() { - if (!AccessibilityManager.getInstance(mContext).isEnabled()) { + if (!mAccessibilityManager.isEnabled()) { return; } final View focusedView = mView.findFocus(); @@ -5179,8 +5179,12 @@ public final class ViewRootImpl implements ViewParent, } private boolean getAccessibilityFocusedRect(Rect bounds) { - final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext); - if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) { + if (mView == null) { + Slog.w(TAG, "calling getAccessibilityFocusedRect() while the mView is null"); + return false; + } + if (!mAccessibilityManager.isEnabled() + || !mAccessibilityManager.isTouchExplorationEnabled()) { return false; } @@ -7248,8 +7252,8 @@ public final class ViewRootImpl implements ViewParent, && action != MotionEvent.ACTION_HOVER_EXIT) { return; } - AccessibilityManager manager = AccessibilityManager.getInstance(mContext); - if (manager.isEnabled() && manager.isTouchExplorationEnabled()) { + if (mAccessibilityManager.isEnabled() + && mAccessibilityManager.isTouchExplorationEnabled()) { return; } if (mView == null) { @@ -11049,7 +11053,7 @@ public final class ViewRootImpl implements ViewParent, return; } // The accessibility may be turned off while we were waiting so check again. - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (mAccessibilityManager.isEnabled()) { mLastEventTimeMillis = SystemClock.uptimeMillis(); AccessibilityEvent event = AccessibilityEvent.obtain(); event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e1de05bda9df..79acfbb0b39c 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -801,6 +801,11 @@ public class RemoteViews implements Parcelable, Filter { mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId, itemsAction.mServiceIntent)); isActionReplaced = true; + } else if (action instanceof SetRemoteViewsAdapterIntent intentAction + && intentAction.viewId == viewId) { + mActions.set(i, new SetRemoteCollectionItemListAdapterAction( + intentAction.viewId, intentAction.intent)); + isActionReplaced = true; } else if (action instanceof ViewGroupActionAdd groupAction && groupAction.mNestedViews != null) { isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId); @@ -822,6 +827,42 @@ public class RemoteViews implements Parcelable, Filter { return isActionReplaced; } + /** + * @return True if has set remote adapter using service intent + * @hide + */ + public boolean hasLegacyLists() { + if (mActions != null) { + for (int i = 0; i < mActions.size(); i++) { + Action action = mActions.get(i); + if ((action instanceof SetRemoteCollectionItemListAdapterAction itemsAction + && itemsAction.mServiceIntent != null) + || (action instanceof SetRemoteViewsAdapterIntent intentAction + && intentAction.intent != null) + || (action instanceof ViewGroupActionAdd groupAction + && groupAction.mNestedViews != null + && groupAction.mNestedViews.hasLegacyLists())) { + return true; + } + } + } + if (mSizedRemoteViews != null) { + for (int i = 0; i < mSizedRemoteViews.size(); i++) { + if (mSizedRemoteViews.get(i).hasLegacyLists()) { + return true; + } + } + } + if (mLandscape != null && mLandscape.hasLegacyLists()) { + return true; + } + if (mPortrait != null && mPortrait.hasLegacyLists()) { + return true; + } + + return false; + } + private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { if (icon != null && (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8899f5cd91d4..0001fe6164a3 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1856,6 +1856,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean clickable = canInputOrMove || isClickable(); boolean longClickable = canInputOrMove || isLongClickable(); int focusable = getFocusable(); + boolean isAutoHandwritingEnabled = true; n = a.getIndexCount(); for (int i = 0; i < n; i++) { @@ -1878,6 +1879,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.View_longClickable: longClickable = a.getBoolean(attr, longClickable); break; + + case com.android.internal.R.styleable.View_autoHandwritingEnabled: + isAutoHandwritingEnabled = a.getBoolean(attr, true); + break; } } a.recycle(); @@ -1891,6 +1896,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } setClickable(clickable); setLongClickable(longClickable); + setAutoHandwritingEnabled(isAutoHandwritingEnabled); if (mEditor != null) mEditor.prepareCursorControllers(); diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig new file mode 100644 index 000000000000..560e41b1aa33 --- /dev/null +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -0,0 +1,10 @@ +package: "com.android.window.flags" + +# Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes + +flag { + namespace: "windowing_sdk" + name: "sync_window_config_update_flag" + description: "Whether the feature to sync different window-related config updates is enabled" + bug: "260873529" +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f55501ab1a8f..ffd640fe36ea 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2267,6 +2267,14 @@ <permission android:name="android.permission.MANAGE_ETHERNET_NETWORKS" android:protectionLevel="signature" /> + <!-- Allows system apps to call methods to register itself as a mDNS offload engine. + <p>Not for use by third-party or privileged applications. + @SystemApi + @hide This should only be used by system apps. + --> + <permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE" + android:protectionLevel="signature" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> diff --git a/core/res/res/anim-ldrtl/activity_close_enter.xml b/core/res/res/anim-ldrtl/activity_close_enter.xml index 6a699e7fcdfa..0b4864600b58 100644 --- a/core/res/res/anim-ldrtl/activity_close_enter.xml +++ b/core/res/res/anim-ldrtl/activity_close_enter.xml @@ -31,7 +31,7 @@ android:duration="450" /> <translate - android:fromXDelta="10%" + android:fromXDelta="96dp" android:toXDelta="0" android:fillEnabled="true" android:fillBefore="true" @@ -41,11 +41,11 @@ android:duration="450" /> <extend - android:fromExtendLeft="10%" + android:fromExtendLeft="96dp" android:fromExtendTop="0" android:fromExtendRight="0" android:fromExtendBottom="0" - android:toExtendLeft="10%" + android:toExtendLeft="96dp" android:toExtendTop="0" android:toExtendRight="0" android:toExtendBottom="0" diff --git a/core/res/res/anim-ldrtl/activity_close_exit.xml b/core/res/res/anim-ldrtl/activity_close_exit.xml index 06a0d6916f61..5277b9fe9947 100644 --- a/core/res/res/anim-ldrtl/activity_close_exit.xml +++ b/core/res/res/anim-ldrtl/activity_close_exit.xml @@ -32,7 +32,7 @@ <translate android:fromXDelta="0" - android:toXDelta="-10%" + android:toXDelta="-96dp" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" @@ -43,11 +43,11 @@ <extend android:fromExtendLeft="0" android:fromExtendTop="0" - android:fromExtendRight="10%" + android:fromExtendRight="96dp" android:fromExtendBottom="0" android:toExtendLeft="0" android:toExtendTop="0" - android:toExtendRight="10%" + android:toExtendRight="96dp" android:toExtendBottom="0" android:interpolator="@interpolator/fast_out_extra_slow_in" android:startOffset="0" diff --git a/core/res/res/anim-ldrtl/activity_open_enter.xml b/core/res/res/anim-ldrtl/activity_open_enter.xml index 7b1829466884..97d2cf9b959f 100644 --- a/core/res/res/anim-ldrtl/activity_open_enter.xml +++ b/core/res/res/anim-ldrtl/activity_open_enter.xml @@ -30,7 +30,7 @@ android:duration="83" /> <translate - android:fromXDelta="-10%" + android:fromXDelta="-96dp" android:toXDelta="0" android:fillEnabled="true" android:fillBefore="true" @@ -41,11 +41,11 @@ <extend android:fromExtendLeft="0" android:fromExtendTop="0" - android:fromExtendRight="10%" + android:fromExtendRight="96dp" android:fromExtendBottom="0" android:toExtendLeft="0" android:toExtendTop="0" - android:toExtendRight="10%" + android:toExtendRight="96dp" android:toExtendBottom="0" android:interpolator="@interpolator/fast_out_extra_slow_in" android:startOffset="0" diff --git a/core/res/res/anim-ldrtl/activity_open_exit.xml b/core/res/res/anim-ldrtl/activity_open_exit.xml index c29509ef1b2a..2159029b784e 100644 --- a/core/res/res/anim-ldrtl/activity_open_exit.xml +++ b/core/res/res/anim-ldrtl/activity_open_exit.xml @@ -31,7 +31,7 @@ <translate android:fromXDelta="0" - android:toXDelta="10%" + android:toXDelta="96dp" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" @@ -40,11 +40,11 @@ android:duration="450" /> <extend - android:fromExtendLeft="10%" + android:fromExtendLeft="9dp" android:fromExtendTop="0" android:fromExtendRight="0" android:fromExtendBottom="0" - android:toExtendLeft="10%" + android:toExtendLeft="96dp" android:toExtendTop="0" android:toExtendRight="0" android:toExtendBottom="0" diff --git a/core/res/res/anim-ldrtl/task_fragment_close_exit.xml b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml index c5a36542cc7d..58fcb1f8fb79 100644 --- a/core/res/res/anim-ldrtl/task_fragment_close_exit.xml +++ b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml @@ -30,7 +30,7 @@ <translate android:fromXDelta="0" - android:toXDelta="-10%" + android:toXDelta="-96dp" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml index 0fefb5113dfc..22a1dd691b8f 100644 --- a/core/res/res/anim/activity_close_enter.xml +++ b/core/res/res/anim/activity_close_enter.xml @@ -31,7 +31,7 @@ android:duration="450" /> <translate - android:fromXDelta="-10%" + android:fromXDelta="-96dp" android:toXDelta="0" android:fillEnabled="true" android:fillBefore="true" @@ -43,11 +43,11 @@ <extend android:fromExtendLeft="0" android:fromExtendTop="0" - android:fromExtendRight="10%" + android:fromExtendRight="96dp" android:fromExtendBottom="0" android:toExtendLeft="0" android:toExtendTop="0" - android:toExtendRight="10%" + android:toExtendRight="96dp" android:toExtendBottom="0" android:interpolator="@interpolator/fast_out_extra_slow_in" android:startOffset="0" diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml index f807c26dda20..a6710490d820 100644 --- a/core/res/res/anim/activity_close_exit.xml +++ b/core/res/res/anim/activity_close_exit.xml @@ -32,7 +32,7 @@ <translate android:fromXDelta="0" - android:toXDelta="10%" + android:toXDelta="96dp" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" @@ -41,11 +41,11 @@ android:duration="450" /> <extend - android:fromExtendLeft="10%" + android:fromExtendLeft="96dp" android:fromExtendTop="0" android:fromExtendRight="0" android:fromExtendBottom="0" - android:toExtendLeft="10%" + android:toExtendLeft="96dp" android:toExtendTop="0" android:toExtendRight="0" android:toExtendBottom="0" diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml index 1674dab3040a..f3172e4cace9 100644 --- a/core/res/res/anim/activity_open_enter.xml +++ b/core/res/res/anim/activity_open_enter.xml @@ -30,7 +30,7 @@ android:duration="83" /> <translate - android:fromXDelta="10%" + android:fromXDelta="96dp" android:toXDelta="0" android:fillEnabled="true" android:fillBefore="true" @@ -39,11 +39,11 @@ android:duration="450" /> <extend - android:fromExtendLeft="10%" + android:fromExtendLeft="96dp" android:fromExtendTop="0" android:fromExtendRight="0" android:fromExtendBottom="0" - android:toExtendLeft="10%" + android:toExtendLeft="96dp" android:toExtendTop="0" android:toExtendRight="0" android:toExtendBottom="0" diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml index 372f2c8e09f6..d84827b835d3 100644 --- a/core/res/res/anim/activity_open_exit.xml +++ b/core/res/res/anim/activity_open_exit.xml @@ -31,7 +31,7 @@ <translate android:fromXDelta="0" - android:toXDelta="-10%" + android:toXDelta="-96dp" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" @@ -42,11 +42,11 @@ <extend android:fromExtendLeft="0" android:fromExtendTop="0" - android:fromExtendRight="10%" + android:fromExtendRight="96dp" android:fromExtendBottom="0" android:toExtendLeft="0" android:toExtendTop="0" - android:toExtendRight="10%" + android:toExtendRight="96dp" android:toExtendBottom="0" android:interpolator="@interpolator/fast_out_extra_slow_in" android:startOffset="0" diff --git a/core/res/res/anim/task_fragment_close_exit.xml b/core/res/res/anim/task_fragment_close_exit.xml index 84d8b7e6b5cd..64540852eea1 100644 --- a/core/res/res/anim/task_fragment_close_exit.xml +++ b/core/res/res/anim/task_fragment_close_exit.xml @@ -30,7 +30,7 @@ <translate android:fromXDelta="0" - android:toXDelta="10%" + android:toXDelta="96dp" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" diff --git a/core/res/res/anim/task_fragment_open_enter.xml b/core/res/res/anim/task_fragment_open_enter.xml index 87ee17925ca7..5f57ed54bf07 100644 --- a/core/res/res/anim/task_fragment_open_enter.xml +++ b/core/res/res/anim/task_fragment_open_enter.xml @@ -27,7 +27,7 @@ android:startOffset="50" android:duration="83" /> <translate - android:fromXDelta="10%" + android:fromXDelta="96dp" android:toXDelta="0" android:fillEnabled="true" android:fillBefore="true" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index aeeaacaacf11..281053b6ce7b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1210,13 +1210,16 @@ <integer name="config_triplePressOnStemPrimaryBehavior">0</integer> <!-- Control the behavior when the user short presses the stem primary button. - Stem primary button is only used on watch form factor. If a device is not - a watch, setting this config is no-op. - 0 - Nothing - 1 - Go to launch all apps + Stem primary button is only used on watch form factor. If a device is not + a watch, setting this config is no-op. + 0 - Nothing + 1 - Go to launch all apps + 2 - Launch target activity defined by config_primaryShortPressTargetActivity if available --> <integer name="config_shortPressOnStemPrimaryBehavior">0</integer> + <!-- Activity name for the default target activity to be launched. [DO NOT TRANSLATE] --> + <string name="config_primaryShortPressTargetActivity" translatable="false"></string> <!-- Control the behavior of the search key. 0 - Launch default search activity @@ -5356,6 +5359,7 @@ <item>1,1,1.0,0,1</item> <item>1,1,1.0,.4,1</item> <item>1,1,1.0,.15,15</item> + <item>0,0,0.7,0,1</item> </string-array> <!-- The integer index of the selected option in config_udfps_touch_detection_options --> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index bda194add759..6bb87f3c4e99 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -73,7 +73,7 @@ CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY. If 0, the device always switch to the higher score SIM. If < 0, the network type and signal strength based auto switch is disabled. --> - <integer name="auto_data_switch_score_tolerance">3000</integer> + <integer name="auto_data_switch_score_tolerance">-1</integer> <java-symbol type="integer" name="auto_data_switch_score_tolerance" /> <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8330f7bdc251..aa0cbfa92043 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -466,6 +466,7 @@ <java-symbol type="integer" name="config_shortPressOnSleepBehavior" /> <java-symbol type="integer" name="config_longPressOnStemPrimaryBehavior" /> <java-symbol type="integer" name="config_shortPressOnStemPrimaryBehavior" /> + <java-symbol type="string" name="config_primaryShortPressTargetActivity" /> <java-symbol type="integer" name="config_doublePressOnStemPrimaryBehavior" /> <java-symbol type="integer" name="config_triplePressOnStemPrimaryBehavior" /> <java-symbol type="string" name="config_doublePressOnPowerTargetActivity" /> diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt index 9acb99a8c1c2..b794352e5e27 100644 --- a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt +++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt @@ -445,7 +445,9 @@ class PackageSessionTests { Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION) } handlerThread = HandlerThread("PackageSessionTests") - handlerThread?.start() - handler = Handler(handlerThread?.looper) + handlerThread?.let { + it.start() + handler = Handler(it.looper) + } } } diff --git a/core/tests/coretests/res/values-id/strings.xml b/core/tests/coretests/res/values-id/strings.xml new file mode 100644 index 000000000000..6d71c90866cc --- /dev/null +++ b/core/tests/coretests/res/values-id/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Used in ResourcesLocaleTest. --> + <string name="locale_test_res_1">Pengujian ID</string> +</resources> diff --git a/core/tests/coretests/res/values-in/strings.xml b/core/tests/coretests/res/values-in/strings.xml new file mode 100644 index 000000000000..63846603ed60 --- /dev/null +++ b/core/tests/coretests/res/values-in/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Used in ResourcesLocaleTest. --> + <string name="locale_test_res_2">Pengujian IN</string> +</resources> diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml index e51eab60a998..09e1c690f4e2 100644 --- a/core/tests/coretests/res/values/strings.xml +++ b/core/tests/coretests/res/values/strings.xml @@ -131,6 +131,13 @@ <string name="textview_hebrew_text">םמab?!</string> + <!-- Used in ResourcesLocaleTest. Also defined in values-id. "id" is the new ISO code for Indonesian. --> + <string name="locale_test_res_1">Testing ID</string> + <!-- Used in ResourcesLocaleTest. Also defined in values-in. "in" is the deprecated ISO code for Indonesian. --> + <string name="locale_test_res_2">Testing IN</string> + <!-- Used in ResourcesLocaleTest. --> + <string name="locale_test_res_3">Testing EN</string> + <!-- SizeAdaptiveLayout --> <string name="first">Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.</string> <string name="actor">Abe Lincoln</string> diff --git a/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java b/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java index 25c3db5c6910..26e4349243a5 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java @@ -16,6 +16,7 @@ package android.content.res; +import android.content.Context; import android.os.FileUtils; import android.os.LocaleList; import android.platform.test.annotations.Presubmit; @@ -97,4 +98,24 @@ public class ResourcesLocaleTest extends AndroidTestCase { assertEquals(Locale.forLanguageTag("pl-PL"), resources.getConfiguration().getLocales().get(0)); } + + @SmallTest + public void testDeprecatedISOLanguageCode() { + assertResGetString(Locale.US, R.string.locale_test_res_1, "Testing ID"); + assertResGetString(Locale.forLanguageTag("id"), R.string.locale_test_res_2, "Pengujian IN"); + assertResGetString(Locale.forLanguageTag("id"), R.string.locale_test_res_3, "Testing EN"); + assertResGetString(new Locale("id"), R.string.locale_test_res_2, "Pengujian IN"); + assertResGetString(new Locale("id"), R.string.locale_test_res_3, "Testing EN"); + // The new ISO code "id" isn't supported yet, and thus the values-id are ignored. + assertResGetString(new Locale("id"), R.string.locale_test_res_1, "Testing ID"); + assertResGetString(Locale.forLanguageTag("id"), R.string.locale_test_res_1, "Testing ID"); + } + + private void assertResGetString(Locale locale, int resId, String expectedString) { + LocaleList locales = new LocaleList(locale); + final Configuration config = new Configuration(); + config.setLocales(locales); + Context newContext = getContext().createConfigurationContext(config); + assertEquals(expectedString, newContext.getResources().getString(resId)); + } } diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index 394ff0ae9a2e..a0d8183b8da7 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -505,45 +505,32 @@ public class FileUtilsTest { @Test public void testRoundStorageSize() throws Exception { - final long GB1 = DataUnit.GIGABYTES.toBytes(1); - final long GiB1 = DataUnit.GIBIBYTES.toBytes(1); - final long GB2 = DataUnit.GIGABYTES.toBytes(2); - final long GiB2 = DataUnit.GIBIBYTES.toBytes(2); - final long GiB128 = DataUnit.GIBIBYTES.toBytes(128); - final long GB256 = DataUnit.GIGABYTES.toBytes(256); - final long GiB256 = DataUnit.GIBIBYTES.toBytes(256); - final long GB512 = DataUnit.GIGABYTES.toBytes(512); - final long GiB512 = DataUnit.GIBIBYTES.toBytes(512); - final long TB1 = DataUnit.TERABYTES.toBytes(1); - final long TiB1 = DataUnit.TEBIBYTES.toBytes(1); - final long TB2 = DataUnit.TERABYTES.toBytes(2); - final long TiB2 = DataUnit.TEBIBYTES.toBytes(2); - final long TB4 = DataUnit.TERABYTES.toBytes(4); - final long TiB4 = DataUnit.TEBIBYTES.toBytes(4); - final long TB8 = DataUnit.TERABYTES.toBytes(8); - final long TiB8 = DataUnit.TEBIBYTES.toBytes(8); - - assertEquals(GB1, roundStorageSize(GB1 - 1)); - assertEquals(GB1, roundStorageSize(GB1)); - assertEquals(GB1, roundStorageSize(GB1 + 1)); - assertEquals(GB1, roundStorageSize(GiB1 - 1)); - assertEquals(GB1, roundStorageSize(GiB1)); - assertEquals(GB2, roundStorageSize(GiB1 + 1)); - assertEquals(GB2, roundStorageSize(GiB2)); - - assertEquals(GB256, roundStorageSize(GiB128 + 1)); - assertEquals(GB256, roundStorageSize(GiB256)); - assertEquals(GB512, roundStorageSize(GiB256 + 1)); - assertEquals(GB512, roundStorageSize(GiB512)); - assertEquals(TB1, roundStorageSize(GiB512 + 1)); - assertEquals(TB1, roundStorageSize(TiB1)); - assertEquals(TB2, roundStorageSize(TiB1 + 1)); - assertEquals(TB2, roundStorageSize(TiB2)); - assertEquals(TB4, roundStorageSize(TiB2 + 1)); - assertEquals(TB4, roundStorageSize(TiB4)); - assertEquals(TB8, roundStorageSize(TiB4 + 1)); - assertEquals(TB8, roundStorageSize(TiB8)); - assertEquals(TB1, roundStorageSize(1013077688320L)); // b/268571529 + final long M256 = DataUnit.MEGABYTES.toBytes(256); + final long M512 = DataUnit.MEGABYTES.toBytes(512); + final long G1 = DataUnit.GIGABYTES.toBytes(1); + final long G2 = DataUnit.GIGABYTES.toBytes(2); + final long G32 = DataUnit.GIGABYTES.toBytes(32); + final long G64 = DataUnit.GIGABYTES.toBytes(64); + final long G512 = DataUnit.GIGABYTES.toBytes(512); + final long G1000 = DataUnit.TERABYTES.toBytes(1); + final long G2000 = DataUnit.TERABYTES.toBytes(2); + + assertEquals(M256, roundStorageSize(M256 - 1)); + assertEquals(M256, roundStorageSize(M256)); + assertEquals(M512, roundStorageSize(M256 + 1)); + assertEquals(M512, roundStorageSize(M512 - 1)); + assertEquals(M512, roundStorageSize(M512)); + assertEquals(G1, roundStorageSize(M512 + 1)); + assertEquals(G1, roundStorageSize(G1)); + assertEquals(G2, roundStorageSize(G1 + 1)); + + assertEquals(G32, roundStorageSize(G32 - 1)); + assertEquals(G32, roundStorageSize(G32)); + assertEquals(G64, roundStorageSize(G32 + 1)); + + assertEquals(G512, roundStorageSize(G512 - 1)); + assertEquals(G1000, roundStorageSize(G512 + 1)); + assertEquals(G2000, roundStorageSize(G1000 + 1)); } @Test diff --git a/core/tests/coretests/src/android/service/euicc/OWNERS b/core/tests/coretests/src/android/service/euicc/OWNERS new file mode 100644 index 000000000000..41fc56b45dde --- /dev/null +++ b/core/tests/coretests/src/android/service/euicc/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/telephony/java/android/service/euicc/OWNERS diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index f1eef7551dd5..c46118db617f 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -245,7 +245,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() { - View delegateView = new View(mContext); + View delegateView = new EditText(mContext); delegateView.setIsHandwritingDelegate(true); mTestView1.setHandwritingDelegatorCallback( @@ -266,7 +266,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() { - View delegateView = new View(mContext); + View delegateView = new EditText(mContext); delegateView.setIsHandwritingDelegate(true); mHandwritingInitiator.onInputConnectionCreated(delegateView); reset(mHandwritingInitiator); diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java new file mode 100644 index 000000000000..a8b40325a713 --- /dev/null +++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window.flags; + +import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link com.android.window.flags.Flags} + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowFlagsTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowFlagsTest { + + @Test + public void testSyncWindowConfigUpdateFlag() { + // No crash when accessing the flag. + syncWindowConfigUpdateFlag(); + } +} diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java index caa7312a4475..ad5c4ee83e54 100644 --- a/core/tests/utiltests/src/android/util/IntArrayTest.java +++ b/core/tests/utiltests/src/android/util/IntArrayTest.java @@ -55,9 +55,11 @@ public class IntArrayTest { a.add(5, 20); assertThat(a.get(5)).isEqualTo(20); assertThat(a.indexOf(20)).isEqualTo(5); + assertThat(a.contains(20)).isTrue(); verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0); assertThat(a.indexOf(99)).isEqualTo(-1); + assertThat(a.contains(99)).isFalse(); a.resize(15); a.set(14, 30); @@ -71,6 +73,7 @@ public class IntArrayTest { backingArray[2] = 30; verify(a, backingArray); assertThat(a.indexOf(30)).isEqualTo(2); + assertThat(a.contains(30)).isTrue(); a.resize(2); assertThat(backingArray[2]).isEqualTo(0); diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index e27cd978e6be..31092536bac5 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -439,8 +439,10 @@ key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE key usage 0x0c0173 MEDIA_AUDIO_TRACK key usage 0x0c019C PROFILE_SWITCH key usage 0x0c01A2 ALL_APPS -key usage 0x0d0044 STYLUS_BUTTON_PRIMARY -key usage 0x0d005a STYLUS_BUTTON_SECONDARY +# TODO(b/297094448): Add stylus button mappings as a fallback when we have a way to determine +# if a device can actually report it. +# key usage 0x0d0044 STYLUS_BUTTON_PRIMARY +# key usage 0x0d005a STYLUS_BUTTON_SECONDARY # Joystick and game controller axes. # Axes that are not mapped will be assigned generic axis numbers by the input subsystem. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 81384ca35d52..a5ee19e2d068 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -261,10 +261,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Updates the Split final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); final WindowContainerTransaction wct = transactionRecord.getTransaction(); - - mPresenter.setTaskFragmentIsolatedNavigation(wct, - splitPinContainer.getSecondaryContainer().getTaskFragmentToken(), - true /* isolatedNav */); mPresenter.updateSplitContainer(splitPinContainer, wct); transactionRecord.apply(false /* shouldApplyIndependently */); updateCallbackIfNecessary(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 5de6acfcc9db..896fe61b5611 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -382,6 +382,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken(), splitRule, isStacked); + + // Setting isolated navigation and clear non-sticky pinned container if needed. + final SplitPinRule splitPinRule = + splitRule instanceof SplitPinRule ? (SplitPinRule) splitRule : null; + if (splitPinRule == null) { + return; + } + + setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(), + !isStacked /* isolatedNav */); + if (isStacked && !splitPinRule.isSticky()) { + secondaryContainer.getTaskContainer().removeSplitPinContainer(); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 4640106b5f1c..9b8006362c79 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -245,8 +245,8 @@ class ActivityEmbeddingAnimationSpec { private boolean shouldShowBackdrop(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change) { - final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE, - mTransitionAnimation, false); + final Animation a = loadAttributeAnimation(info.getType(), info, change, + WALLPAPER_TRANSITION_NONE, mTransitionAnimation, false); return a != null && a.getShowBackdrop(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index ff67110634ba..9a2b81243861 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -851,7 +851,10 @@ public class Bubble implements BubbleViewProvider { return mAppIntent; } - boolean isAppBubble() { + /** + * Returns whether this bubble is from an app versus a notification. + */ + public boolean isAppBubble() { return mIsAppBubble; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java index 250e010f4d69..76662c47238f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java @@ -52,6 +52,11 @@ public class BubbleDebugConfig { private static final boolean FORCE_SHOW_USER_EDUCATION = false; private static final String FORCE_SHOW_USER_EDUCATION_SETTING = "force_show_bubbles_user_education"; + /** + * When set to true, bubbles user education flow never shows up. + */ + private static final String FORCE_HIDE_USER_EDUCATION_SETTING = + "force_hide_bubbles_user_education"; /** * @return whether we should force show user education for bubbles. Used for debugging & demos. @@ -62,6 +67,14 @@ public class BubbleDebugConfig { return FORCE_SHOW_USER_EDUCATION || forceShow; } + /** + * @return whether we should never show user education for bubbles. Used in tests. + */ + static boolean neverShowUserEducation(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + FORCE_HIDE_USER_EDUCATION_SETTING, 0) != 0; + } + static String formatBubblesString(List<Bubble> bubbles, BubbleViewProvider selected) { StringBuilder sb = new StringBuilder(); for (Bubble bubble : bubbles) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 2c100653dae0..ea7053d8ee49 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -653,14 +653,38 @@ public class BubblePositioner { } /** - * @return the stack position to use if we don't have a saved location or if user education - * is being shown. + * Returns whether the {@link #getRestingPosition()} is equal to the default start position + * initialized for bubbles, if {@code true} this means the user hasn't moved the bubble + * from the initial start position (or they haven't received a bubble yet). + */ + public boolean hasUserModifiedDefaultPosition() { + PointF defaultStart = getDefaultStartPosition(); + return mRestingStackPosition != null + && !mRestingStackPosition.equals(defaultStart); + } + + /** + * Returns the stack position to use if we don't have a saved location or if user education + * is being shown, for a normal bubble. */ public PointF getDefaultStartPosition() { - // Start on the left if we're in LTR, right otherwise. - final boolean startOnLeft = - mContext.getResources().getConfiguration().getLayoutDirection() - != LAYOUT_DIRECTION_RTL; + return getDefaultStartPosition(false /* isAppBubble */); + } + + /** + * The stack position to use if we don't have a saved location or if user education + * is being shown. + * + * @param isAppBubble whether this start position is for an app bubble or not. + */ + public PointF getDefaultStartPosition(boolean isAppBubble) { + final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection(); + // Normal bubbles start on the left if we're in LTR, right otherwise. + // TODO (b/294284894): update language around "app bubble" here + // App bubbles start on the right in RTL, left otherwise. + final boolean startOnLeft = isAppBubble + ? layoutDirection == LAYOUT_DIRECTION_RTL + : layoutDirection != LAYOUT_DIRECTION_RTL; final RectF allowableStackPositionRegion = getAllowableStackPositionRegion( 1 /* default starts with 1 bubble */); if (isLargeScreen()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 8ae12c75b3ee..52c9bf8462ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1284,6 +1284,12 @@ public class BubbleStackView extends FrameLayout if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show manage edu: " + shouldShow); } + if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { + if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { + Log.d(TAG, "Want to show manage edu, but it is forced hidden"); + } + return false; + } return shouldShow; } @@ -1316,6 +1322,12 @@ public class BubbleStackView extends FrameLayout if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show stack edu: " + shouldShow); } + if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { + if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { + Log.d(TAG, "Want to show stack edu, but it is forced hidden"); + } + return false; + } return shouldShow; } @@ -1763,13 +1775,26 @@ public class BubbleStackView extends FrameLayout return; } + if (firstBubble && bubble.isAppBubble() && !mPositioner.hasUserModifiedDefaultPosition()) { + // TODO (b/294284894): update language around "app bubble" here + // If it's an app bubble and we don't have a previous resting position, update the + // controllers to use the default position for the app bubble (it'd be different from + // the position initialized with the controllers originally). + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition); + mStackAnimationController.setStackPosition(startPosition); + mExpandedAnimationController.setCollapsePoint(startPosition); + // Set the translation x so that this bubble will animate in from the same side they + // expand / collapse on. + bubble.getIconView().setTranslationX(startPosition.x); + } else if (firstBubble) { + mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); + } + mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); - if (firstBubble) { - mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); - } // Set the dot position to the opposite of the side the stack is resting on, since the stack // resting slightly off-screen would result in the dot also being off-screen. bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index c20733af2ba5..4d7042bbb3d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -131,6 +131,16 @@ public class ExpandedAnimationController private BubbleStackView mBubbleStackView; + /** + * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause + * the rest of the bubbles to animate to fill the gap. + */ + private boolean mBubbleDraggedOutEnough = false; + + /** End action to run when the lead bubble's expansion animation completes. */ + @Nullable + private Runnable mLeadBubbleEndAction; + public ExpandedAnimationController(BubblePositioner positioner, Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) { mPositioner = positioner; @@ -141,14 +151,12 @@ public class ExpandedAnimationController } /** - * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause - * the rest of the bubbles to animate to fill the gap. + * Overrides the collapse location without actually collapsing the stack. + * @param point the new collapse location. */ - private boolean mBubbleDraggedOutEnough = false; - - /** End action to run when the lead bubble's expansion animation completes. */ - @Nullable - private Runnable mLeadBubbleEndAction; + public void setCollapsePoint(PointF point) { + mCollapsePoint = point; + } /** * Animates expanding the bubbles into a row along the top of the screen, optionally running an diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 4bb1ab4d0f54..aad268394305 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -297,9 +297,6 @@ public class StackAnimationController extends /** Whether the stack is on the left side of the screen. */ public boolean isStackOnLeftSide() { - if (mLayout == null || !isStackPositionSet()) { - return true; // Default to left, which is where it starts by default. - } return mPositioner.isStackOnLeft(mStackPosition); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt index 8b5283d83683..1fd22d0a3505 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt @@ -85,7 +85,7 @@ class BubblePopupDrawable(private val config: Config) : Drawable() { canvas.drawPath(path, paint) } - override fun onBoundsChange(bounds: Rect?) { + override fun onBoundsChange(bounds: Rect) { requestPathUpdate() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt index a141ff951684..4abb35c2a428 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt @@ -19,7 +19,6 @@ import android.app.AppOpsManager import android.content.Context import android.content.pm.PackageManager import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.pip.PipUtils class PipAppOpsListener( private val mContext: Context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt index 2719cd29009e..427a555eee92 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt @@ -33,7 +33,6 @@ import android.os.Handler import android.os.HandlerExecutor import android.os.UserHandle import com.android.wm.shell.R -import com.android.wm.shell.pip.PipUtils import java.util.function.Consumer /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt new file mode 100644 index 000000000000..84feb03e6a40 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.common.pip + +import android.app.ActivityTaskManager +import android.app.RemoteAction +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.Context +import android.os.RemoteException +import android.os.SystemProperties +import android.util.DisplayMetrics +import android.util.Log +import android.util.Pair +import android.util.TypedValue +import android.window.TaskSnapshot +import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.protolog.ShellProtoLogGroup +import kotlin.math.abs + +/** A class that includes convenience methods. */ +object PipUtils { + private const val TAG = "PipUtils" + + // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. + private const val EPSILON = 1e-7 + private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation" + + /** + * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. + * The component name may be null if no such activity exists. + */ + @JvmStatic + fun getTopPipActivity(context: Context): Pair<ComponentName?, Int> { + try { + val sysUiPackageName = context.packageName + val pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo( + WindowConfiguration.WINDOWING_MODE_PINNED, + WindowConfiguration.ACTIVITY_TYPE_UNDEFINED + ) + if (pinnedTaskInfo?.childTaskIds != null && pinnedTaskInfo.childTaskIds.isNotEmpty()) { + for (i in pinnedTaskInfo.childTaskNames.indices.reversed()) { + val cn = ComponentName.unflattenFromString( + pinnedTaskInfo.childTaskNames[i] + ) + if (cn != null && cn.packageName != sysUiPackageName) { + return Pair(cn, pinnedTaskInfo.childTaskUserIds[i]) + } + } + } + } catch (e: RemoteException) { + ProtoLog.w( + ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Unable to get pinned stack.", TAG + ) + } + return Pair(null, 0) + } + + /** + * @return the pixels for a given dp value. + */ + @JvmStatic + fun dpToPx(dpValue: Float, dm: DisplayMetrics?): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, dm).toInt() + } + + /** + * @return true if the aspect ratios differ + */ + @JvmStatic + fun aspectRatioChanged(aspectRatio1: Float, aspectRatio2: Float): Boolean { + return abs(aspectRatio1 - aspectRatio2) > EPSILON + } + + /** + * Checks whether title, description and intent match. + * Comparing icons would be good, but using equals causes false negatives + */ + @JvmStatic + fun remoteActionsMatch(action1: RemoteAction?, action2: RemoteAction?): Boolean { + if (action1 === action2) return true + if (action1 == null || action2 == null) return false + return action1.isEnabled == action2.isEnabled && + action1.shouldShowIcon() == action2.shouldShowIcon() && + action1.title == action2.title && + action1.contentDescription == action2.contentDescription && + action1.actionIntent == action2.actionIntent + } + + /** + * Returns true if the actions in the lists match each other according to + * [ ][PipUtils.remoteActionsMatch], including their position. + */ + @JvmStatic + fun remoteActionsChanged(list1: List<RemoteAction?>?, list2: List<RemoteAction?>?): Boolean { + if (list1 == null && list2 == null) { + return false + } + if (list1 == null || list2 == null) { + return true + } + if (list1.size != list2.size) { + return true + } + for (i in list1.indices) { + if (!remoteActionsMatch(list1[i], list2[i])) { + return true + } + } + return false + } + + /** @return [TaskSnapshot] for a given task id. + */ + @JvmStatic + fun getTaskSnapshot(taskId: Int, isLowResolution: Boolean): TaskSnapshot? { + return if (taskId <= 0) null else try { + ActivityTaskManager.getService().getTaskSnapshot( + taskId, isLowResolution, false /* takeSnapshotIfNeeded */ + ) + } catch (e: RemoteException) { + Log.e(TAG, "Failed to get task snapshot, taskId=$taskId", e) + null + } + } + + @JvmStatic + val isPip2ExperimentEnabled: Boolean + get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 7bf0893c60c7..c111ce623c1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -34,6 +34,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; @@ -59,6 +60,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -80,6 +82,9 @@ public class CompatUIController implements OnDisplaysChangedListener, private static final String TAG = "CompatUIController"; + // The time to wait before education and button hiding + private static final int DISAPPEAR_DELAY_MS = 5000; + /** Whether the IME is shown on display id. */ private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1); @@ -158,6 +163,9 @@ public class CompatUIController implements OnDisplaysChangedListener, @NonNull private final CompatUIShellCommandHandler mCompatUIShellCommandHandler; + @NonNull + private final Function<Integer, Integer> mDisappearTimeSupplier; + @Nullable private CompatUICallback mCompatUICallback; @@ -176,7 +184,8 @@ public class CompatUIController implements OnDisplaysChangedListener, @NonNull Lazy<Transitions> transitionsLazy, @NonNull DockStateReader dockStateReader, @NonNull CompatUIConfiguration compatUIConfiguration, - @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler) { + @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler, + @NonNull AccessibilityManager accessibilityManager) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -189,6 +198,8 @@ public class CompatUIController implements OnDisplaysChangedListener, mDockStateReader = dockStateReader; mCompatUIConfiguration = compatUIConfiguration; mCompatUIShellCommandHandler = compatUIShellCommandHandler; + mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis( + DISAPPEAR_DELAY_MS, flags); shellInit.addInitCallback(this::onInit, this); } @@ -510,7 +521,8 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellTaskOrganizer.TaskListener taskListener) { return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), - mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed); + mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed, + mDisappearTimeSupplier); } private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo, @@ -556,7 +568,8 @@ public class CompatUIController implements OnDisplaysChangedListener, @Nullable ShellTaskOrganizer.TaskListener taskListener) { return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), - mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor); + mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor, + mDisappearTimeSupplier); } private void launchUserAspectRatioSettings( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java index 9de3f9dec34e..5612bc8ef226 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java @@ -28,6 +28,7 @@ import android.os.SystemClock; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; @@ -37,15 +38,13 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import java.util.function.BiConsumer; +import java.util.function.Function; /** * Window manager for the reachability education */ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { - // The time to wait before hiding the education - private static final long DISAPPEAR_DELAY_MS = 4000L; - private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0; private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2; @@ -77,6 +76,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback; + private final Function<Integer, Integer> mDisappearTimeSupplier; + @Nullable @VisibleForTesting ReachabilityEduLayout mLayout; @@ -85,7 +86,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor, - BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback) { + BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback, + Function<Integer, Integer> disappearTimeSupplier) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled; mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition; @@ -95,6 +97,7 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { mCompatUIConfiguration = compatUIConfiguration; mMainExecutor = mainExecutor; mOnDismissCallback = onDismissCallback; + mDisappearTimeSupplier = disappearTimeSupplier; } @Override @@ -215,7 +218,12 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { } void updateHideTime() { - mNextHideTime = SystemClock.uptimeMillis() + DISAPPEAR_DELAY_MS; + mNextHideTime = SystemClock.uptimeMillis() + getDisappearTimeMs(); + } + + private long getDisappearTimeMs() { + return mDisappearTimeSupplier.apply( + AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_TEXT); } private void updateVisibilityOfViews() { @@ -248,14 +256,15 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { availableHeight, mCompatUIConfiguration, lastTaskInfo); if (!mHasLetterboxSizeChanged) { updateHideTime(); - mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS); + final long disappearTimeMs = getDisappearTimeMs(); + mMainExecutor.executeDelayed(this::hideReachability, disappearTimeMs); // If reachability education has been seen for the first time, trigger callback to // display aspect ratio settings button once reachability education disappears if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu) || hasShownVerticalReachabilityEduFirstTime( hasSeenVerticalReachabilityEdu)) { mMainExecutor.executeDelayed(this::triggerOnDismissCallback, - DISAPPEAR_DELAY_MS); + disappearTimeMs); } } mHasUserDoubleTapped = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index bd53dc7390c8..cbff4640239e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.os.SystemClock; import android.view.LayoutInflater; import android.view.View; +import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; @@ -36,6 +37,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import java.util.function.BiConsumer; +import java.util.function.Function; /** * Window manager for the user aspect ratio settings button which allows users to go to @@ -45,12 +47,12 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L; - private static final long HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 4000L; - private long mNextButtonHideTimeMs = -1L; private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked; + private final Function<Integer, Integer> mDisappearTimeSupplier; + private final ShellExecutor mShellExecutor; @VisibleForTesting @@ -69,12 +71,14 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract @Nullable ShellTaskOrganizer.TaskListener taskListener, @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState, @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked, - @NonNull ShellExecutor shellExecutor) { + @NonNull ShellExecutor shellExecutor, + @NonNull Function<Integer, Integer> disappearTimeSupplier) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mShellExecutor = shellExecutor; mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo); mCompatUIHintsState = compatUIHintsState; mOnButtonClicked = onButtonClicked; + mDisappearTimeSupplier = disappearTimeSupplier; } @Override @@ -140,9 +144,9 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract return; } mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true); - mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); - mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, - HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + final long disappearTimeMs = getDisappearTimeMs(); + mNextButtonHideTimeMs = updateHideTime(disappearTimeMs); + mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs); } @Override @@ -167,9 +171,9 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract if (mHasUserAspectRatioSettingsButton) { mShellExecutor.executeDelayed(this::showUserAspectRatioButton, SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS); - mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); - mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, - HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + final long disappearTimeMs = getDisappearTimeMs(); + mNextButtonHideTimeMs = updateHideTime(disappearTimeMs); + mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs); } else { mShellExecutor.removeCallbacks(this::showUserAspectRatioButton); mShellExecutor.execute(this::hideUserAspectRatioButton); @@ -208,4 +212,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract && (taskInfo.topActivityBoundsLetterboxed || taskInfo.isUserFullscreenOverrideEnabled); } + + private long getDisappearTimeMs() { + return mDisappearTimeSupplier.apply(AccessibilityManager.FLAG_CONTENT_CONTROLS); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 94723416cdf8..e28b8d78103c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; +import android.view.accessibility.AccessibilityManager; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; @@ -233,10 +234,12 @@ public abstract class WMShellBaseModule { DisplayImeController imeController, SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration, - CompatUIShellCommandHandler compatUIShellCommandHandler) { + CompatUIShellCommandHandler compatUIShellCommandHandler, + AccessibilityManager accessibilityManager) { return new CompatUIController(context, shellInit, shellController, displayController, displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy, - dockStateReader, compatUIConfiguration, compatUIShellCommandHandler); + dockStateReader, compatUIConfiguration, compatUIShellCommandHandler, + accessibilityManager); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 24ef44a7c0f4..4e92ca113114 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -34,6 +34,7 @@ import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; @@ -50,7 +51,6 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransition; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PipController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java index 04032bb17fec..9c9364e17e0e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java @@ -18,9 +18,9 @@ package com.android.wm.shell.dagger.pip; import android.annotation.Nullable; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipUtils; import dagger.Module; import dagger.Provides; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 633f627e8e71..b0f75c6a1e6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -311,7 +311,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW) - wct.setBounds(task.token, null) + wct.setBounds(task.token, Rect()) wct.setDensityDpi(task.token, getDefaultDensityDpi()) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index ac711eafe3ef..4fef672b2cd8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -28,6 +28,7 @@ import android.util.Size; import android.view.Gravity; import com.android.wm.shell.R; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import java.io.PrintWriter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java index 456f85b52da4..4aa260b44646 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java @@ -16,7 +16,7 @@ package com.android.wm.shell.pip; -import static com.android.wm.shell.pip.PipUtils.dpToPx; +import static com.android.wm.shell.common.pip.PipUtils.dpToPx; import android.content.Context; import android.content.res.Resources; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 296857b196cf..ed9ff1c169c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -83,6 +83,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index db7e2c0c529f..83e03dc850a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -64,6 +64,7 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 0f74f9e323a5..64bba672a5b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -38,6 +38,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java deleted file mode 100644 index 3cd9848d5686..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.pip; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.util.TypedValue.COMPLEX_UNIT_DIP; - -import android.annotation.Nullable; -import android.app.ActivityTaskManager; -import android.app.ActivityTaskManager.RootTaskInfo; -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.Context; -import android.os.RemoteException; -import android.os.SystemProperties; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.Pair; -import android.util.TypedValue; -import android.window.TaskSnapshot; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.protolog.ShellProtoLogGroup; - -import java.util.List; -import java.util.Objects; - -/** A class that includes convenience methods. */ -public class PipUtils { - private static final String TAG = "PipUtils"; - - // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. - private static final double EPSILON = 1e-7; - - private static final String ENABLE_PIP2_IMPLEMENTATION = - "persist.wm.debug.enable_pip2_implementation"; - - /** - * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. - * The component name may be null if no such activity exists. - */ - public static Pair<ComponentName, Integer> getTopPipActivity(Context context) { - try { - final String sysUiPackageName = context.getPackageName(); - final RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (pinnedTaskInfo != null && pinnedTaskInfo.childTaskIds != null - && pinnedTaskInfo.childTaskIds.length > 0) { - for (int i = pinnedTaskInfo.childTaskNames.length - 1; i >= 0; i--) { - ComponentName cn = ComponentName.unflattenFromString( - pinnedTaskInfo.childTaskNames[i]); - if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) { - return new Pair<>(cn, pinnedTaskInfo.childTaskUserIds[i]); - } - } - } - } catch (RemoteException e) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Unable to get pinned stack.", TAG); - } - return new Pair<>(null, 0); - } - - /** - * @return the pixels for a given dp value. - */ - public static int dpToPx(float dpValue, DisplayMetrics dm) { - return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm); - } - - /** - * @return true if the aspect ratios differ - */ - public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) { - return Math.abs(aspectRatio1 - aspectRatio2) > EPSILON; - } - - /** - * Checks whether title, description and intent match. - * Comparing icons would be good, but using equals causes false negatives - */ - public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) { - if (action1 == action2) return true; - if (action1 == null || action2 == null) return false; - return action1.isEnabled() == action2.isEnabled() - && action1.shouldShowIcon() == action2.shouldShowIcon() - && Objects.equals(action1.getTitle(), action2.getTitle()) - && Objects.equals(action1.getContentDescription(), action2.getContentDescription()) - && Objects.equals(action1.getActionIntent(), action2.getActionIntent()); - } - - /** - * Returns true if the actions in the lists match each other according to {@link - * PipUtils#remoteActionsMatch(RemoteAction, RemoteAction)}, including their position. - */ - public static boolean remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2) { - if (list1 == null && list2 == null) { - return false; - } - if (list1 == null || list2 == null) { - return true; - } - if (list1.size() != list2.size()) { - return true; - } - for (int i = 0; i < list1.size(); i++) { - if (!remoteActionsMatch(list1.get(i), list2.get(i))) { - return true; - } - } - return false; - } - - /** @return {@link TaskSnapshot} for a given task id. */ - @Nullable - public static TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) { - if (taskId <= 0) return null; - try { - return ActivityTaskManager.getService().getTaskSnapshot( - taskId, isLowResolution, false /* takeSnapshotIfNeeded */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e); - return null; - } - } - - public static boolean isPip2ExperimentEnabled() { - return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 5c65d7839f07..ddea574c3c89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -77,6 +77,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.IPip; @@ -93,7 +94,6 @@ import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 837426ae3aab..fc34772f2fce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -67,7 +67,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUiEventLogger; -import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 6055fd9d9593..cf54a7163d04 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -51,13 +51,13 @@ import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java index f1606f6a8ca5..6b890c49b713 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java @@ -36,7 +36,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; -import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 7c1563787a12..57439a59ccca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -55,7 +55,7 @@ import com.android.internal.widget.LinearLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.wm.shell.R; import com.android.wm.shell.common.TvWindowMenuActionButton; -import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index 39c7a4b220a7..1c94625ddde9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -37,8 +37,8 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ImageUtils; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index dbec607c2ae9..672080407658 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -26,6 +26,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -36,7 +37,6 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.Objects; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS new file mode 100644 index 000000000000..ec09827fa4d1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS @@ -0,0 +1,3 @@ +# WM shell sub-module pip owner +hwwang@google.com +mateuszc@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 7df658e6c9db..d310ae32993c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -37,8 +37,12 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; +import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; @@ -334,6 +338,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { boolean isDisplayRotationAnimationStarted = false; final boolean isDreamTransition = isDreamTransition(info); final boolean isOnlyTranslucent = isOnlyTranslucent(info); + final boolean isActivityReplace = checkActivityReplacement(info, startTransaction); + // Some patterns (eg. activity "replacement") require us to re-interpret the type + @WindowManager.TransitionType final int transitType = + isActivityReplace ? TRANSIT_OPEN : info.getType(); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -430,7 +438,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // Don't animate anything that isn't independent. if (!TransitionInfo.isIndependent(change, info)) continue; - Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition); + Animation a = loadAnimation(transitType, info, change, wallpaperTransit, + isDreamTransition); if (a != null) { if (isTask) { final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0; @@ -604,6 +613,53 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return (translucentOpen + translucentClose) > 0; } + /** + * Checks for an edge-case where an activity calls finish() followed immediately by + * startActivity() to "replace" itself. If in this case, it will swap the layer of the + * close/open activities and return `true`. This way, we pretend like we are just "opening" + * the new activity. + */ + private static boolean checkActivityReplacement(@NonNull TransitionInfo info, + SurfaceControl.Transaction t) { + if (info.getType() != TRANSIT_CLOSE) { + return false; + } + int closing = -1; + int opening = -1; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY)) + && !TransitionUtil.isOrderOnly(change)) { + // This isn't an activity-level transition. + return false; + } + if (change.getTaskInfo() != null + && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) { + // Ignore non-activity containers. + continue; + } + if (TransitionUtil.isClosingType(change.getMode())) { + closing = i; + } else if (change.getMode() == TRANSIT_OPEN) { + // OPEN implies that it is a new launch. If going "back" the opening app will be + // TO_FRONT + opening = i; + } else if (change.getMode() == TRANSIT_TO_FRONT) { + // Normal "going back", so not a replacement. + return false; + } + } + if (closing < 0 || opening < 0) { + return false; + } + // Swap the opening and closing z-orders since we're swapping the transit type. + final int numChanges = info.getChanges().size(); + final int zSplitLine = numChanges + 1; + t.setLayer(info.getChanges().get(opening).getLeash(), zSplitLine + numChanges - opening); + t.setLayer(info.getChanges().get(closing).getLeash(), zSplitLine - closing); + return true; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -656,12 +712,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } @Nullable - private Animation loadAnimation(@NonNull TransitionInfo info, + private Animation loadAnimation(int type, @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, boolean isDreamTransition) { Animation a; - final int type = info.getType(); final int flags = info.getFlags(); final int changeMode = change.getMode(); final int changeFlags = change.getFlags(); @@ -716,8 +771,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // If there's a scene-transition, then jump-cut. return null; } else { - a = loadAttributeAnimation( - info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition); + a = loadAttributeAnimation(type, info, change, wallpaperTransit, mTransitionAnimation, + isDreamTransition); } if (a != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index d07d2b7b6db9..c99911de5291 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -45,6 +45,7 @@ import android.graphics.Rect; import android.graphics.Shader; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.Transformation; import android.window.ScreenCapture; @@ -61,10 +62,10 @@ public class TransitionAnimationHelper { /** Loads the animation that is defined through attribute id for the given transition. */ @Nullable - public static Animation loadAttributeAnimation(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, int wallpaperTransit, - @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) { - final int type = info.getType(); + public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type, + @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, + int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation, + boolean isDreamTransition) { final int changeMode = change.getMode(); final int changeFlags = change.getFlags(); final boolean enter = TransitionUtil.isOpeningType(changeMode); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java index 367676f54aba..f7a060f0638b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java @@ -46,5 +46,7 @@ public interface ShellUnfoldProgressProvider { default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {} default void onStateChangeFinished() {} + + default void onFoldStateChanged(boolean isFolded) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index 2eb6e71456db..68b5a81f8d7b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -63,6 +63,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Nullable private IBinder mTransition; + private boolean mAnimationFinished = false; private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>(); public UnfoldTransitionHandler(ShellInit shellInit, @@ -132,6 +133,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene startTransaction.apply(); mFinishCallback = finishCallback; + + // Shell transition started when unfold animation has already finished, + // finish shell transition immediately + if (mAnimationFinished) { + finishTransitionIfNeeded(); + } + return true; } @@ -161,17 +169,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public void onStateChangeFinished() { - if (mFinishCallback == null) return; - - for (int i = 0; i < mAnimators.size(); i++) { - final UnfoldTaskAnimator animator = mAnimators.get(i); - animator.clearTasks(); - animator.stop(); - } - - mFinishCallback.onTransitionFinished(null); - mFinishCallback = null; - mTransition = null; + mAnimationFinished = true; + finishTransitionIfNeeded(); } @Override @@ -218,4 +217,25 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene public boolean willHandleTransition() { return mTransition != null; } + + @Override + public void onFoldStateChanged(boolean isFolded) { + if (isFolded) { + mAnimationFinished = false; + } + } + + private void finishTransitionIfNeeded() { + if (mFinishCallback == null) return; + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.clearTasks(); + animator.stop(); + } + + mFinishCallback.onTransitionFinished(null); + mFinishCallback = null; + mTransition = null; + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index 97147a3e1672..bc095bbacc5a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -31,6 +31,7 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.LaunchBubbleHelper +import com.android.server.wm.flicker.helpers.MultiWindowUtils import com.android.wm.shell.flicker.BaseTest import org.junit.runners.Parameterized @@ -56,6 +57,9 @@ abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) ): FlickerBuilder.() -> Unit { return { setup { + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings put secure force_hide_bubbles_user_education 1") notifyManager.setBubblesAllowed( testApp.packageName, uid, @@ -67,6 +71,9 @@ abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) } teardown { + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings put secure force_hide_bubbles_user_education 0") notifyManager.setBubblesAllowed( testApp.packageName, uid, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java index 139724f709c7..58d9a6486ff2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -203,6 +203,60 @@ public class BubblePositionerTest extends ShellTestCase { assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); } + /** Test that the default resting position on tablet is middle right. */ + @Test + public void testGetDefaultPosition_appBubble_onTablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + + assertThat(startPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_appBubble_onTablet_RTL() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + + assertThat(startPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testHasUserModifiedDefaultPosition_false() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + + mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition()); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + } + + @Test + public void testHasUserModifiedDefaultPosition_true() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + + mPositioner.setRestingPosition(new PointF(0, 100)); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue(); + } + /** * Calculates the Y position bubbles should be placed based on the config. Based on * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index efc69ebd395c..9b9600e4a51e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -41,6 +41,7 @@ import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.view.InsetsSource; import android.view.InsetsState; +import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; @@ -113,6 +114,9 @@ public class CompatUIControllerTest extends ShellTestCase { @Mock private CompatUIShellCommandHandler mCompatUIShellCommandHandler; + @Mock + private AccessibilityManager mAccessibilityManager; + @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; @@ -139,7 +143,7 @@ public class CompatUIControllerTest extends ShellTestCase { mController = new CompatUIController(mContext, mShellInit, mMockShellController, mMockDisplayController, mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader, - mCompatUIConfiguration, mCompatUIShellCommandHandler) { + mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java index a802f15a0a41..5867a8553d53 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java @@ -109,6 +109,6 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) { return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue, mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor, - mOnDismissCallback); + mOnDismissCallback, flags -> 0); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java index 1fee153877b5..ce1290b38830 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java @@ -93,7 +93,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIController.CompatUIHintsState(), - mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor()); + mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor(), flags -> 0); mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate( R.layout.user_aspect_ratio_settings_layout, null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index b48538ca99ca..08cc2f763135 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -106,7 +106,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { false, /* topActivityBoundsLetterboxed */ true); mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), - mOnUserAspectRatioSettingsButtonClicked, mExecutor); + mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0); spyOn(mWindowManager); doReturn(mLayout).when(mWindowManager).inflateLayout(); doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 99a1ac663286..a57a7bf4c659 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -99,6 +99,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -1061,7 +1062,8 @@ public class ShellTransitionTests extends ShellTestCase { mTransactionPool, createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor); final RecentsTransitionHandler recentsHandler = - new RecentsTransitionHandler(shellInit, transitions, null); + new RecentsTransitionHandler(shellInit, transitions, + mock(RecentTasksController.class)); transitions.replaceDefaultHandlerForTest(mDefaultHandler); shellInit.init(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java new file mode 100644 index 000000000000..63a685e0f0e6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.unfold; + +import static android.view.WindowManager.TRANSIT_CHANGE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.os.Binder; +import android.os.IBinder; +import android.view.Display; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; +import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; +import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +public class UnfoldTransitionHandlerTest { + + private UnfoldTransitionHandler mUnfoldTransitionHandler; + + private final TestShellUnfoldProgressProvider mShellUnfoldProgressProvider = + new TestShellUnfoldProgressProvider(); + private final TestTransactionPool mTransactionPool = new TestTransactionPool(); + + private FullscreenUnfoldTaskAnimator mFullscreenUnfoldTaskAnimator; + private SplitTaskUnfoldAnimator mSplitTaskUnfoldAnimator; + private Transitions mTransitions; + + private final IBinder mTransition = new Binder(); + + @Before + public void before() { + final ShellExecutor executor = new TestSyncExecutor(); + final ShellInit shellInit = new ShellInit(executor); + + mFullscreenUnfoldTaskAnimator = mock(FullscreenUnfoldTaskAnimator.class); + mSplitTaskUnfoldAnimator = mock(SplitTaskUnfoldAnimator.class); + mTransitions = mock(Transitions.class); + + mUnfoldTransitionHandler = new UnfoldTransitionHandler( + shellInit, + mShellUnfoldProgressProvider, + mFullscreenUnfoldTaskAnimator, + mSplitTaskUnfoldAnimator, + mTransactionPool, + executor, + mTransitions + ); + + shellInit.init(); + } + + @Test + public void handleRequest_physicalDisplayChange_handlesTransition() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange); + + WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, + requestInfo); + + assertThat(result).isNotNull(); + } + + @Test + public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(false); + TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange); + + WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, + requestInfo); + + assertThat(result).isNull(); + } + + @Test + public void startAnimation_animationHasNotFinishedYet_doesNotFinishTheTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + verify(finishCallback, never()).onTransitionFinished(any()); + } + + @Test + public void startAnimation_animationFinishes_finishesTheTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + + verify(finishCallback).onTransitionFinished(any()); + } + + @Test + public void startAnimation_animationIsAlreadyFinished_finishesTheTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + verify(finishCallback).onTransitionFinished(any()); + } + + @Test + public void startAnimationSecondTimeAfterFold_animationAlreadyFinished_finishesTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + // First unfold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false); + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + clearInvocations(finishCallback); + + // Fold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true); + + // Second unfold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false); + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + verify(finishCallback).onTransitionFinished(any()); + } + + private TransitionRequestInfo createUnfoldTransitionRequestInfo() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + return new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange); + } + + private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider, + ShellUnfoldProgressProvider.UnfoldListener { + + private final List<UnfoldListener> mListeners = new ArrayList<>(); + + @Override + public void addListener(Executor executor, UnfoldListener listener) { + mListeners.add(listener); + } + + @Override + public void onFoldStateChanged(boolean isFolded) { + mListeners.forEach(unfoldListener -> unfoldListener.onFoldStateChanged(isFolded)); + } + + @Override + public void onStateChangeFinished() { + mListeners.forEach(UnfoldListener::onStateChangeFinished); + } + + @Override + public void onStateChangeProgress(float progress) { + mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress)); + } + + @Override + public void onStateChangeStarted() { + mListeners.forEach(UnfoldListener::onStateChangeStarted); + } + } + + private static class TestTransactionPool extends TransactionPool { + @Override + public SurfaceControl.Transaction acquire() { + return mock(SurfaceControl.Transaction.class); + } + + @Override + public void release(SurfaceControl.Transaction t) { + } + } + + private static class TestSyncExecutor implements ShellExecutor { + @Override + public void execute(Runnable runnable) { + runnable.run(); + } + + @Override + public void executeDelayed(Runnable runnable, long delayMillis) { + runnable.run(); + } + + @Override + public void removeCallbacks(Runnable runnable) { + } + + @Override + public boolean hasCallback(Runnable runnable) { + return false; + } + } +}
\ No newline at end of file diff --git a/location/Android.bp b/location/Android.bp new file mode 100644 index 000000000000..46dca74e7e40 --- /dev/null +++ b/location/Android.bp @@ -0,0 +1,41 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +//location sources that will populate the new module +filegroup { + name: "framework-location-nonupdatable-sources", + srcs: [ + "placeholder_java/android/location/Placeholder.java", + ], +} + +java_sdk_library { + name: "framework-location", + srcs: [ + ":framework-location-nonupdatable-sources", + ], + defaults: ["framework-non-updatable-unbundled-defaults"], + permitted_packages: [ + "android.location", + "com.android.internal.location", + ], + libs: [ + "app-compat-annotations", + "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage + ], + hidden_api_packages: [ + "com.android.internal.location", + ], + aidl: { + include_dirs: [ + "frameworks/base/location/java", + "frameworks/base/core/java", + ], + }, +} diff --git a/location/api/current.txt b/location/api/current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/module-lib-current.txt b/location/api/module-lib-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/module-lib-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/module-lib-removed.txt b/location/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/removed.txt b/location/api/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/system-current.txt b/location/api/system-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/system-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/system-removed.txt b/location/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/test-current.txt b/location/api/test-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/test-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/test-removed.txt b/location/api/test-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/test-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/java/android/location/GnssRequest.java b/location/java/android/location/GnssRequest.java index 9c9766fd02d6..f3a40cc3af40 100644 --- a/location/java/android/location/GnssRequest.java +++ b/location/java/android/location/GnssRequest.java @@ -41,7 +41,7 @@ public final class GnssRequest implements Parcelable { * * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock * discontinuities are expected, and when supported, carrier phase should be continuous in - * good signal conditions. All non-blacklisted, healthy constellations, satellites and + * good signal conditions. All non-denylisted, healthy constellations, satellites and * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via * duty cycling, constellations and frequency limits, etc. @@ -138,7 +138,7 @@ public final class GnssRequest implements Parcelable { * * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock * discontinuities are expected, and when supported, carrier phase should be continuous in - * good signal conditions. All non-blacklisted, healthy constellations, satellites and + * good signal conditions. All non-denylisted, healthy constellations, satellites and * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via * duty cycling, constellations and frequency limits, etc. diff --git a/location/placeholder_java/android/location/Placeholder.java b/location/placeholder_java/android/location/Placeholder.java new file mode 100644 index 000000000000..f0dbce829174 --- /dev/null +++ b/location/placeholder_java/android/location/Placeholder.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.location; + +/** + * Placeholder class so new frameworks-location module isn't empty, will be removed once module is + * populated. + * + * @hide + * + */ +public class Placeholder { +} diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING index 5ae77b5a8e2f..a9da832b2a5a 100644 --- a/media/TEST_MAPPING +++ b/media/TEST_MAPPING @@ -37,6 +37,17 @@ } ], "file_patterns": ["(?i)drm|crypto"] + }, + { + "file_patterns": [ + "[^/]*(Ringtone)[^/]*\\.java" + ], + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "androidx.test.filters.LargeTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] } ] } diff --git a/media/java/android/media/Cea708CaptionRenderer.java b/media/java/android/media/Cea708CaptionRenderer.java index 88912fef4501..efaf3d22f489 100644 --- a/media/java/android/media/Cea708CaptionRenderer.java +++ b/media/java/android/media/Cea708CaptionRenderer.java @@ -194,7 +194,7 @@ class Cea708CCParser { private final StringBuffer mBuffer = new StringBuffer(); private int mCommand = 0; - // Assign a dummy listener in order to avoid null checks. + // Assign a placeholder listener in order to avoid null checks. private DisplayListener mListener = new DisplayListener() { @Override public void emitEvent(CaptionEvent event) { diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 0ff1b1e19811..9234479ea8fd 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -16,6 +16,7 @@ package android.media; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -35,19 +36,20 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.StaleDataException; import android.net.Uri; -import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.vibrator.persistence.VibrationXmlParser; import android.provider.BaseColumns; import android.provider.MediaStore; import android.provider.MediaStore.Audio.AudioColumns; import android.provider.MediaStore.MediaColumns; import android.provider.Settings; import android.provider.Settings.System; +import android.text.TextUtils; import android.util.Log; import com.android.internal.database.SortCursor; @@ -58,6 +60,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -209,21 +213,30 @@ public class RingtoneManager { */ public static final String EXTRA_RINGTONE_PICKED_URI = "android.intent.extra.ringtone.PICKED_URI"; - + + /** + * Declares the allowed types of media for this RingtoneManager. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "MEDIA_", value = { + Ringtone.MEDIA_SOUND, + Ringtone.MEDIA_VIBRATION, + }) + public @interface MediaType {} + // Make sure the column ordering and then ..._COLUMN_INDEX are in sync - private static final String[] INTERNAL_COLUMNS = new String[] { + private static final String[] MEDIA_AUDIO_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.TITLE_KEY, }; - private static final String[] MEDIA_COLUMNS = new String[] { - MediaStore.Audio.Media._ID, - MediaStore.Audio.Media.TITLE, - MediaStore.Audio.Media.TITLE, - MediaStore.Audio.Media.TITLE_KEY, + private static final String[] MEDIA_VIBRATION_COLUMNS = new String[]{ + MediaStore.Files.FileColumns._ID, + MediaStore.Files.FileColumns.TITLE, }; /** @@ -251,7 +264,9 @@ public class RingtoneManager { private Cursor mCursor; private int mType = TYPE_RINGTONE; - + @MediaType + private int mMediaType = Ringtone.MEDIA_SOUND; + /** * If a column (item from this list) exists in the Cursor, its value must * be true (value of 1) for the row to be returned. @@ -318,6 +333,41 @@ public class RingtoneManager { } /** + * Sets the media type that will be listed by the RingtoneManager. + * + * <p>This method should be called before calling {@link RingtoneManager#getCursor()}. + * + * @hide + */ + public void setMediaType(@MediaType int mediaType) { + if (mCursor != null) { + throw new IllegalStateException( + "Setting media should be done before calling getCursor()."); + } + + switch (mediaType) { + case Ringtone.MEDIA_SOUND: + case Ringtone.MEDIA_VIBRATION: + mMediaType = mediaType; + break; + default: + throw new IllegalArgumentException("Unsupported media type " + mediaType); + } + } + + /** + * Returns the RingtoneManagers media type. + * + * @return the media type. + * @see #setMediaType + * @hide + */ + @MediaType + public int getMediaType() { + return mMediaType; + } + + /** * Sets which type(s) of ringtones will be listed by this. * * @param type The type(s), one or more of {@link #TYPE_RINGTONE}, @@ -454,19 +504,19 @@ public class RingtoneManager { return mCursor; } - ArrayList<Cursor> ringtoneCursors = new ArrayList<Cursor>(); - ringtoneCursors.add(getInternalRingtones()); - ringtoneCursors.add(getMediaRingtones()); + ArrayList<Cursor> cursors = new ArrayList<>(); + + cursors.add(queryMediaStore(/* internal= */ true)); + cursors.add(queryMediaStore(/* internal= */ false)); if (mIncludeParentRingtones) { Cursor parentRingtonesCursor = getParentProfileRingtones(); if (parentRingtonesCursor != null) { - ringtoneCursors.add(parentRingtonesCursor); + cursors.add(parentRingtonesCursor); } } - - return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]), - MediaStore.Audio.Media.DEFAULT_SORT_ORDER); + return mCursor = new SortCursor(cursors.toArray(new Cursor[cursors.size()]), + getSortOrderForMedia(mMediaType)); } private Cursor getParentProfileRingtones() { @@ -478,9 +528,7 @@ public class RingtoneManager { // We don't need to re-add the internal ringtones for the work profile since // they are the same as the personal profile. We just need the external // ringtones. - final Cursor res = getMediaRingtones(parentContext); - return new ExternalRingtonesCursorWrapper(res, ContentProvider.maybeAddUserId( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, parentInfo.id)); + return queryMediaStore(parentContext, /* internal= */ false); } } return null; @@ -502,7 +550,7 @@ public class RingtoneManager { Uri positionUri = getRingtoneUri(position); if (Ringtone.useRingtoneV2()) { mPreviousRingtone = new Ringtone.Builder( - mContext, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(mType)) + mContext, mMediaType, getDefaultAudioAttributes(mType)) .setUri(positionUri) .build(); } else { @@ -675,11 +723,13 @@ public class RingtoneManager { */ public static Uri getValidRingtoneUri(Context context) { final RingtoneManager rm = new RingtoneManager(context); - - Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones()); + + Uri uri = getValidRingtoneUriFromCursorAndClose(context, + rm.queryMediaStore(/* internal= */ true)); if (uri == null) { - uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); + uri = getValidRingtoneUriFromCursorAndClose(context, + rm.queryMediaStore(/* internal= */ false)); } return uri; @@ -700,28 +750,26 @@ public class RingtoneManager { } } - @UnsupportedAppUsage - private Cursor getInternalRingtones() { - final Cursor res = query( - MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns), - null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); - return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI); + private Cursor queryMediaStore(boolean internal) { + return queryMediaStore(mContext, internal); } - private Cursor getMediaRingtones() { - final Cursor res = getMediaRingtones(mContext); - return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); - } + private Cursor queryMediaStore(Context context, boolean internal) { + Uri contentUri = getContentUriForMedia(mMediaType, internal); + String[] columns = + mMediaType == Ringtone.MEDIA_VIBRATION ? MEDIA_VIBRATION_COLUMNS + : MEDIA_AUDIO_COLUMNS; + String whereClause = getWhereClauseForMedia(mMediaType, mFilterColumns); + String sortOrder = getSortOrderForMedia(mMediaType); + + Cursor cursor = query(contentUri, columns, whereClause, /* selectionArgs= */ null, + sortOrder, context); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private Cursor getMediaRingtones(Context context) { - // MediaStore now returns ringtones on other storage devices, even when - // we don't have storage or audio permissions - return query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns), null, - MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context); + if (context.getUserId() != mContext.getUserId()) { + contentUri = ContentProvider.maybeAddUserId(contentUri, context.getUserId()); + } + + return new ExternalRingtonesCursorWrapper(cursor, contentUri); } private void setFilterColumnsList(int type) { @@ -740,6 +788,56 @@ public class RingtoneManager { columns.add(MediaStore.Audio.AudioColumns.IS_ALARM); } } + + /** + * Returns the sort order for the specified media. + * + * @param media The RingtoneManager media type. + * @return The sort order column. + */ + private static String getSortOrderForMedia(@MediaType int media) { + return media == Ringtone.MEDIA_VIBRATION ? MediaStore.Files.FileColumns.TITLE + : MediaStore.Audio.Media.DEFAULT_SORT_ORDER; + } + + /** + * Returns the content URI based on the specified media and whether it's internal or external + * storage. + * + * @param media The RingtoneManager media type. + * @param internal Whether it's for internal or external storage. + * @return The media content URI. + */ + private static Uri getContentUriForMedia(@MediaType int media, boolean internal) { + switch (media) { + case Ringtone.MEDIA_VIBRATION: + return MediaStore.Files.getContentUri( + internal ? MediaStore.VOLUME_INTERNAL : MediaStore.VOLUME_EXTERNAL); + case Ringtone.MEDIA_SOUND: + return internal ? MediaStore.Audio.Media.INTERNAL_CONTENT_URI + : MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + default: + throw new IllegalArgumentException("Unsupported media type " + media); + } + } + + /** + * Constructs a where clause based on the media type. This will be used to find all matching + * sound or vibration files. + * + * @param media The RingtoneManager media type. + * @param columns The columns that must be true, when media type is {@link Ringtone#MEDIA_SOUND} + * @return The where clause. + */ + private static String getWhereClauseForMedia(@MediaType int media, List<String> columns) { + // TODO(b/296213309): Filtering by ringtone-type isn't supported yet for vibrations. + if (media == Ringtone.MEDIA_VIBRATION) { + return TextUtils.formatSimple("(%s='%s')", MediaStore.Files.FileColumns.MIME_TYPE, + VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE); + } + + return constructBooleanTrueWhereClause(columns); + } /** * Constructs a where clause that consists of at least one column being 1 @@ -769,14 +867,6 @@ public class RingtoneManager { return sb.toString(); } - - private Cursor query(Uri uri, - String[] projection, - String selection, - String[] selectionArgs, - String sortOrder) { - return query(uri, projection, selection, selectionArgs, sortOrder, mContext); - } private Cursor query(Uri uri, String[] projection, diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java index 5a690a57dbb2..b4bf428d6e7e 100644 --- a/media/java/android/media/voice/KeyphraseModelManager.java +++ b/media/java/android/media/voice/KeyphraseModelManager.java @@ -37,7 +37,7 @@ import java.util.Objects; * This class provides management of voice based sound recognition models. Usage of this class is * restricted to system or signature applications only. This allows OEMs to write apps that can * manage voice based sound trigger models. - * Callers of this class are expected to have whitelist manifest permission MANAGE_VOICE_KEYPHRASES. + * Callers of this class are expected to have allowlist manifest permission MANAGE_VOICE_KEYPHRASES. * Callers of this class are expected to be the designated voice interaction service via * {@link Settings.Secure.VOICE_INTERACTION_SERVICE} or a bundled voice model enrollment application * detected by {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}. diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp new file mode 100644 index 000000000000..55b98c4704b1 --- /dev/null +++ b/media/tests/ringtone/Android.bp @@ -0,0 +1,30 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "MediaRingtoneTests", + + srcs: ["src/**/*.java"], + + libs: [ + "android.test.runner", + "android.test.base", + ], + + static_libs: [ + "androidx.test.rules", + "testng", + "androidx.test.ext.truth", + "frameworks-base-testutils", + ], + + test_suites: [ + "device-tests", + "automotive-tests", + ], + + platform_apis: true, + certificate: "platform", +} diff --git a/media/tests/ringtone/AndroidManifest.xml b/media/tests/ringtone/AndroidManifest.xml new file mode 100644 index 000000000000..27eda07cd0d3 --- /dev/null +++ b/media/tests/ringtone/AndroidManifest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.framework.base.media.ringtone.tests"> + + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + + <activity android:name="MediaRingtoneTests" + android:label="Media Ringtone Tests" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.framework.base.media.ringtone.tests" + android:label="Media Ringtone Tests"/> +</manifest> diff --git a/media/tests/ringtone/TEST_MAPPING b/media/tests/ringtone/TEST_MAPPING new file mode 100644 index 000000000000..6f25c147076c --- /dev/null +++ b/media/tests/ringtone/TEST_MAPPING @@ -0,0 +1,20 @@ +{ + "presubmit": [ + { + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "androidx.test.filters.LargeTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ], + "postsubmit": [ + { + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ] +}
\ No newline at end of file diff --git a/media/tests/ringtone/res/raw/test_haptic_file.ahv b/media/tests/ringtone/res/raw/test_haptic_file.ahv new file mode 100644 index 000000000000..18c99c79814f --- /dev/null +++ b/media/tests/ringtone/res/raw/test_haptic_file.ahv @@ -0,0 +1,17 @@ +<vibration> + <waveform-effect> + <waveform-entry durationMs="63" amplitude="255"/> + <waveform-entry durationMs="63" amplitude="231"/> + <waveform-entry durationMs="63" amplitude="208"/> + <waveform-entry durationMs="63" amplitude="185"/> + <waveform-entry durationMs="63" amplitude="162"/> + <waveform-entry durationMs="63" amplitude="139"/> + <waveform-entry durationMs="63" amplitude="115"/> + <waveform-entry durationMs="63" amplitude="92"/> + <waveform-entry durationMs="63" amplitude="69"/> + <waveform-entry durationMs="63" amplitude="46"/> + <waveform-entry durationMs="63" amplitude="23"/> + <waveform-entry durationMs="63" amplitude="0"/> + <waveform-entry durationMs="1250" amplitude="0"/> + </waveform-effect> +</vibration> diff --git a/media/tests/ringtone/res/raw/test_sound_file.mp3 b/media/tests/ringtone/res/raw/test_sound_file.mp3 Binary files differnew file mode 100644 index 000000000000..c1b2fdf93991 --- /dev/null +++ b/media/tests/ringtone/res/raw/test_sound_file.mp3 diff --git a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java new file mode 100644 index 000000000000..a92b29883ce7 --- /dev/null +++ b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.media; + +import static com.google.android.mms.ContentType.AUDIO_MP3; +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.os.vibrator.persistence.VibrationXmlParser; +import android.provider.MediaStore; +import android.text.TextUtils; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.framework.base.media.ringtone.tests.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(Parameterized.class) +public class RingtoneManagerTest { + @RingtoneManager.MediaType + private final int mMediaType; + private final List<Uri> mAddedFilesUri; + private Context mContext; + private RingtoneManager mRingtoneManager; + private long mTimestamp; + + @Parameterized.Parameters(name = "media = {0}") + public static Iterable<?> data() { + return Arrays.asList(Ringtone.MEDIA_SOUND, Ringtone.MEDIA_VIBRATION); + } + + public RingtoneManagerTest(@RingtoneManager.MediaType int mediaType) { + mMediaType = mediaType; + mAddedFilesUri = new ArrayList<>(); + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mTimestamp = SystemClock.uptimeMillis(); + mRingtoneManager = new RingtoneManager(mContext); + mRingtoneManager.setMediaType(mMediaType); + } + + @After + public void tearDown() { + // Clean up media store + for (Uri fileUri : mAddedFilesUri) { + mContext.getContentResolver().delete(fileUri, null); + } + } + + @Test + public void testSetMediaType_withValidValue_setsMediaCorrectly() { + mRingtoneManager.setMediaType(mMediaType); + assertThat(mRingtoneManager.getMediaType()).isEqualTo(mMediaType); + } + + @Test + public void testSetMediaType_withInvalidValue_throwsException() { + assertThrows(IllegalArgumentException.class, () -> mRingtoneManager.setMediaType(999)); + } + + @Test + public void testSetMediaType_afterCallingGetCursor_throwsException() { + mRingtoneManager.getCursor(); + assertThrows(IllegalStateException.class, () -> mRingtoneManager.setMediaType(mMediaType)); + } + + @Test + public void testGetRingtone_ringtoneHasCorrectTitle() throws Exception { + String fileName = generateUniqueFileName("new_file"); + Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName); + + assertThat(ringtone.getTitle(mContext)).isEqualTo(fileName); + } + + @Test + public void testGetRingtone_ringtoneCanBePlayedAndStopped() throws Exception { + //TODO(b/261571543) Remove this assumption once we support playing vibrations. + assumeTrue(mMediaType == Ringtone.MEDIA_SOUND); + String fileName = generateUniqueFileName("new_file"); + Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName); + + ringtone.play(); + assertThat(ringtone.isPlaying()).isTrue(); + + ringtone.stop(); + assertThat(ringtone.isPlaying()).isFalse(); + } + + @Test + public void testGetCursor_withDifferentMedia_returnsCorrectCursor() throws Exception { + RingtoneManager audioRingtoneManager = new RingtoneManager(mContext); + String audioFileName = generateUniqueFileName("ringtone"); + addNewRingtoneToMediaStore(audioRingtoneManager, audioFileName); + + RingtoneManager vibrationRingtoneManager = new RingtoneManager(mContext); + vibrationRingtoneManager.setMediaType(Ringtone.MEDIA_VIBRATION); + String vibrationFileName = generateUniqueFileName("vibration"); + addNewRingtoneToMediaStore(vibrationRingtoneManager, vibrationFileName); + + Cursor audioCursor = audioRingtoneManager.getCursor(); + Cursor vibrationCursor = vibrationRingtoneManager.getCursor(); + + List<String> audioTitles = extractRecordTitles(audioCursor); + List<String> vibrationTitles = extractRecordTitles(vibrationCursor); + + assertThat(audioTitles).contains(audioFileName); + assertThat(audioTitles).doesNotContain(vibrationFileName); + + assertThat(vibrationTitles).contains(vibrationFileName); + assertThat(vibrationTitles).doesNotContain(audioFileName); + } + + private List<String> extractRecordTitles(Cursor cursor) { + List<String> titles = new ArrayList<>(); + + if (cursor.moveToFirst()) { + do { + String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX); + titles.add(title); + } while (cursor.moveToNext()); + } + + return titles; + } + + private Ringtone addNewRingtoneToMediaStore(RingtoneManager ringtoneManager, String fileName) + throws Exception { + Uri fileUri = ringtoneManager.getMediaType() == Ringtone.MEDIA_SOUND ? addAudioFile( + fileName) : addVibrationFile(fileName); + mAddedFilesUri.add(fileUri); + + int ringtonePosition = ringtoneManager.getRingtonePosition(fileUri); + Ringtone ringtone = ringtoneManager.getRingtone(ringtonePosition); + // Validate this is the expected ringtone. + assertThat(ringtone.getUri()).isEqualTo(fileUri); + return ringtone; + } + + private Uri addAudioFile(String fileName) throws Exception { + ContentResolver resolver = mContext.getContentResolver(); + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName + ".mp3"); + contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_RINGTONES); + contentValues.put(MediaStore.Audio.Media.MIME_TYPE, AUDIO_MP3); + contentValues.put(MediaStore.Audio.Media.TITLE, fileName); + contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, 1); + + Uri contentUri = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + contentValues); + writeRawDataToFile(resolver, contentUri, R.raw.test_sound_file); + + return resolver.canonicalizeOrElse(contentUri); + } + + private Uri addVibrationFile(String fileName) throws Exception { + ContentResolver resolver = mContext.getContentResolver(); + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName + ".ahv"); + contentValues.put(MediaStore.Files.FileColumns.RELATIVE_PATH, + Environment.DIRECTORY_DOWNLOADS); + contentValues.put(MediaStore.Files.FileColumns.MIME_TYPE, + VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE); + contentValues.put(MediaStore.Files.FileColumns.TITLE, fileName); + + Uri contentUri = resolver.insert(MediaStore.Files.getContentUri(MediaStore + .VOLUME_EXTERNAL), contentValues); + writeRawDataToFile(resolver, contentUri, R.raw.test_haptic_file); + + return resolver.canonicalizeOrElse(contentUri); + } + + private void writeRawDataToFile(ContentResolver resolver, Uri contentUri, int rawResource) + throws Exception { + try (ParcelFileDescriptor pfd = + resolver.openFileDescriptor(contentUri, "w", null)) { + InputStream inputStream = mContext.getResources().openRawResource(rawResource); + FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor()); + outputStream.write(inputStream.readAllBytes()); + + inputStream.close(); + outputStream.flush(); + outputStream.close(); + + } catch (Exception e) { + throw new Exception("Failed to write data to file", e); + } + } + + private String generateUniqueFileName(String prefix) { + return TextUtils.formatSimple("%s_%d", prefix, mTimestamp); + } + +} diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index c571b742d2d1..c25df6e08fd0 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -144,8 +144,6 @@ APerformanceHintSession* APerformanceHintManager::createSession( binder::Status ret = mHintManager->createHintSession(mToken, tids, initialTargetWorkDurationNanos, &session); if (!ret.isOk() || !session) { - ALOGE("%s: PerformanceHint cannot create hint session. %s", __FUNCTION__, - ret.exceptionMessage().c_str()); return nullptr; } return new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos, diff --git a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt index 5fa61373a686..562f7be5ae1e 100644 --- a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt +++ b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt @@ -293,7 +293,7 @@ class ClueView(context: Context) : View(context) { return correct } - override fun onDraw(canvas: Canvas?) { + override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (!showText) return canvas?.let { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 5880a29c6c46..334886fe5561 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -48,6 +48,8 @@ import androidx.annotation.StringRes; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; /** * This is a utility class for defining some utility methods and constants @@ -63,15 +65,23 @@ public class PackageUtil { //intent attribute strings related to uninstall public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName"; private static final String DOWNLOADS_AUTHORITY = "downloads"; + private static final String SPLIT_BASE_APK_END_WITH = "base.apk"; /** * Utility method to get package information for a given {@link File} */ @Nullable public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) { + String filePath = sourceFile.getAbsolutePath(); + if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) { + File dir = sourceFile.getParentFile(); + if (dir.listFiles().length > 1) { + // split apks, use file directory to get archive info + filePath = dir.getPath(); + } + } try { - return context.getPackageManager().getPackageArchiveInfo(sourceFile.getAbsolutePath(), - flags); + return context.getPackageManager().getPackageArchiveInfo(filePath, flags); } catch (Exception ignored) { return null; } @@ -191,6 +201,17 @@ public class PackageUtil { PackageManager pm = pContext.getPackageManager(); appInfo.publicSourceDir = archiveFilePath; + if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) { + final File[] files = sourceFile.getParentFile().listFiles(); + final String[] splits = Arrays.stream(appInfo.splitNames) + .map(i -> findFilePath(files, i + ".apk")) + .filter(Objects::nonNull) + .toArray(String[]::new); + + appInfo.splitSourceDirs = splits; + appInfo.splitPublicSourceDirs = splits; + } + CharSequence label = null; // Try to load the label from the package's resources. If an app has not explicitly // specified any label, just use the package name. @@ -223,6 +244,16 @@ public class PackageUtil { return new PackageUtil.AppSnippet(label, icon); } + private static String findFilePath(File[] files, String postfix) { + for (File file : files) { + final String path = file.getAbsolutePath(); + if (path.endsWith(postfix)) { + return path; + } + } + return null; + } + /** * Get the maximum target sdk for a UID. * diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt index 3b9bf47671c9..ad907cfeb5bf 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt @@ -46,7 +46,12 @@ fun DisposableBroadcastReceiverAsUser( LifecycleEffect( onStart = { context.registerReceiverAsUser( - broadcastReceiver, userHandle, intentFilter, null, null + broadcastReceiver, + userHandle, + intentFilter, + null, + null, + Context.RECEIVER_NOT_EXPORTED, ) }, onStop = { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt index 8e0cf894bb28..a428142f4cc8 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt @@ -78,6 +78,7 @@ class AppListRepositoryImpl(private val context: Context) : AppListRepository { packageManager.getInstalledModules(0) .filter { it.isHidden } .map { it.packageName } + .filterNotNull() .toSet() } val hideWhenDisabledPackagesDeferred = async { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt index 69c6131679a9..92fd0cd07777 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt @@ -82,7 +82,8 @@ internal class PackageManagersImpl( val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId) val index = packageInfo?.requestedPermissions?.indexOf(permission) ?: return false return index >= 0 && - packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED) + checkNotNull(packageInfo.requestedPermissionsFlags)[index] + .hasFlag(REQUESTED_PERMISSION_GRANTED) } override suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> = diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt index cc3584b6fb11..dfd8f6b8e373 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt @@ -28,7 +28,7 @@ class EnterpriseRepository(private val context: Context) { } fun getEnterpriseString(updatableStringId: String, resId: Int): String = - resources.getString(updatableStringId) { context.getString(resId) } + checkNotNull(resources.getString(updatableStringId) { context.getString(resId) }) fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) { getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index cee750e186e0..ea83e1d0d3d1 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -58,7 +58,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { .semantics(mergeDescendants = true) {}, horizontalAlignment = Alignment.CenterHorizontally, ) { - val app = packageInfo.applicationInfo + val app = checkNotNull(packageInfo.applicationInfo) Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { AppIcon(app = app, size = SettingsDimension.appIconInfoSize) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 7f82be4df196..62c5f70a63ac 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -144,7 +144,7 @@ internal fun TogglePermissionAppListModel<out AppRecord>.TogglePermissionAppInfo footerContent = { AnnotatedText(footerResId) }, packageManagers = packageManagers, ) { - val model = createSwitchModel(applicationInfo) + val model = createSwitchModel(checkNotNull(applicationInfo)) val restrictions = Restrictions(userId, switchRestrictionKeys) RestrictedSwitchPreference(model, restrictions, restrictionsProviderFactory) } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt index 9bd92423f9d2..2c8fb66fab0a 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt @@ -31,10 +31,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.any import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.isNull +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class DisposableBroadcastReceiverAsUserTest { @@ -51,18 +53,26 @@ class DisposableBroadcastReceiverAsUserTest { @Before fun setUp() { - whenever(context.registerReceiverAsUser(any(), any(), any(), any(), any())) - .thenAnswer { - registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver - null - } + whenever( + context.registerReceiverAsUser( + any(), + eq(USER_HANDLE), + eq(INTENT_FILTER), + isNull(), + isNull(), + eq(Context.RECEIVER_NOT_EXPORTED), + ) + ).then { + registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver + null + } } @Test fun broadcastReceiver_registered() { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { - DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {} + DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {} } } @@ -74,7 +84,7 @@ class DisposableBroadcastReceiverAsUserTest { var onReceiveIsCalled = false composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { - DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) { + DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) { onReceiveIsCalled = true } } @@ -87,5 +97,7 @@ class DisposableBroadcastReceiverAsUserTest { private companion object { val USER_HANDLE: UserHandle = UserHandle.of(0) + + val INTENT_FILTER = IntentFilter() } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt index 2281cd82cc6e..517f67e3c44b 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt @@ -38,16 +38,15 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.eq -import org.mockito.Mockito.verify import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppListRepositoryTest { @@ -75,7 +74,7 @@ class AppListRepositoryTest { .thenReturn(emptyArray()) whenever(context.packageManager).thenReturn(packageManager) whenever(context.userManager).thenReturn(userManager) - whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList()) + whenever(packageManager.getInstalledModules(any())).thenReturn(emptyList()) whenever(packageManager.getHomeActivities(any())).thenAnswer { @Suppress("UNCHECKED_CAST") val resolveInfos = it.arguments[0] as MutableList<ResolveInfo> @@ -83,7 +82,7 @@ class AppListRepositoryTest { null } whenever( - packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt()) + packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), any<Int>()) ).thenReturn(listOf(resolveInfoOf(packageName = IN_LAUNCHER_APP.packageName))) whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply { flags = UserInfo.FLAG_ADMIN @@ -134,12 +133,13 @@ class AppListRepositoryTest { ) assertThat(appList).containsExactly(NORMAL_APP) - val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) - verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) - assertThat(flags.value.value).isEqualTo( - PackageManager.MATCH_DISABLED_COMPONENTS or - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - ) + argumentCaptor<ApplicationInfoFlags> { + verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID)) + assertThat(firstValue.value).isEqualTo( + PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + ) + } } @Test @@ -152,9 +152,11 @@ class AppListRepositoryTest { ) assertThat(appList).containsExactly(NORMAL_APP) - val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) - verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) - assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L) + argumentCaptor<ApplicationInfoFlags> { + verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID)) + assertThat(firstValue.value and PackageManager.MATCH_ANY_USER.toLong()) + .isGreaterThan(0L) + } } @Test diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt index eb3d7dc84dba..91bbd9f9a5c1 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt @@ -32,15 +32,13 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.anyString -import org.mockito.Mockito.doNothing -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppOpsControllerTest { @@ -56,8 +54,8 @@ class AppOpsControllerTest { fun setUp() { whenever(context.appOpsManager).thenReturn(appOpsManager) whenever(context.packageManager).thenReturn(packageManager) - doNothing().`when`(packageManager) - .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any()) + doNothing().whenever(packageManager) + .updatePermissionFlags(any(), any(), any(), any(), any()) } @Test diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt index d11e63ae048f..8f458d33c126 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt @@ -35,7 +35,7 @@ import org.mockito.Mock import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppRepositoryTest { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt index a1b2df3acdfb..7f9e98b95fb7 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt @@ -34,7 +34,7 @@ import org.mockito.Mock import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class ApplicationInfosTest { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt index 40026554882e..e10619e01c4e 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt @@ -29,14 +29,14 @@ import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.any -import org.mockito.Mockito.eq -import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class PackageManagerExtTest { @@ -110,9 +110,10 @@ class PackageManagerExtTest { flags = PackageManager.GET_META_DATA, ) - val flagsCaptor = ArgumentCaptor.forClass(ResolveInfoFlags::class.java) - verify(packageManager).resolveActivityAsUser(any(), flagsCaptor.capture(), eq(APP.userId)) - assertThat(flagsCaptor.value.value).isEqualTo(PackageManager.GET_META_DATA.toLong()) + argumentCaptor<ResolveInfoFlags> { + verify(packageManager).resolveActivityAsUser(any(), capture(), eq(APP.userId)) + assertThat(firstValue.value).isEqualTo(PackageManager.GET_META_DATA.toLong()) + } } private companion object { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt index f4faa0a878ac..eb2055f694e4 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt @@ -38,15 +38,13 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.anyString -import org.mockito.Mockito.doNothing -import org.mockito.Mockito.verify import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppOpPermissionAppListTest { @@ -68,8 +66,8 @@ class AppOpPermissionAppListTest { fun setUp() { whenever(context.appOpsManager).thenReturn(appOpsManager) whenever(context.packageManager).thenReturn(packageManager) - doNothing().`when`(packageManager) - .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any()) + doNothing().whenever(packageManager) + .updatePermissionFlags(any(), any(), any(), any(), any()) listModel = TestAppOpPermissionAppListModel() } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt index e3af58702445..60f3d0ce1be3 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt @@ -38,7 +38,7 @@ import org.mockito.Mock import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppStorageSizeTest { @@ -63,7 +63,9 @@ class AppStorageSizeTest { whenever(context.storageStatsManager).thenReturn(storageStatsManager) whenever( storageStatsManager.queryStatsForPackage( - app.storageUuid, app.packageName, app.userHandle + app.storageUuid, + app.packageName, + app.userHandle, ) ).thenReturn(STATS) } @@ -86,7 +88,9 @@ class AppStorageSizeTest { var storageSize = stateOf("Computing") whenever( storageStatsManager.queryStatsForPackage( - app.storageUuid, app.packageName, app.userHandle + app.storageUuid, + app.packageName, + app.userHandle, ) ).thenThrow(NameNotFoundException()) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt index e37288ab3a71..8bfae14b3b8f 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt @@ -45,7 +45,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class TogglePermissionAppInfoPageTest { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index f12aa26f6778..755d971e28a6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -307,8 +307,8 @@ public class BluetoothUtils { */ public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice, int device) { - return bluetoothDevice.getBluetoothClass() != null - && bluetoothDevice.getBluetoothClass().getDeviceClass() == device; + final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass(); + return bluetoothClass != null && bluetoothClass.getDeviceClass() == device; } private static boolean isAdvancedHeaderEnabled() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index f03ff00828a6..00397f0e646d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -277,6 +277,38 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mRemovedProfiles.add(profile); mLocalNapRoleConnected = false; } + + if (!HearingAidStatsLogUtils.isUserCategorized(mContext)) { + if (HearingAidStatsLogUtils.isJustBonded(getAddress())) { + // Saves bonded timestamp as the source for judging whether to display + // the survey + if (getProfiles().stream().anyMatch( + p -> (p instanceof HearingAidProfile + || p instanceof HapClientProfile))) { + HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, + HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED); + } else if (getProfiles().stream().anyMatch( + p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) { + HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, + HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED); + } + HearingAidStatsLogUtils.removeFromJustBonded(getAddress()); + } + + // Saves connected timestamp as the source for judging whether to display + // the survey + if (newProfileState == BluetoothProfile.STATE_CONNECTED) { + if (profile instanceof HearingAidProfile + || profile instanceof HapClientProfile) { + HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, + HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED); + } else if (profile instanceof A2dpSinkProfile + || profile instanceof HeadsetProfile) { + HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, + HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED); + } + } + } } fetchActiveDevices(); @@ -899,6 +931,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (mDevice.isBondingInitiatedLocally()) { connect(); } + + if (!HearingAidStatsLogUtils.isUserCategorized(mContext)) { + // Saves this device as just bonded and checks if it's an hearing device after + // profiles are connected. This is for judging whether to display the survey. + HearingAidStatsLogUtils.addToJustBonded(getAddress()); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java index 1401a4f923b6..97b94da60274 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java @@ -16,18 +16,74 @@ package com.android.settingslib.bluetooth; +import android.content.Context; +import android.content.SharedPreferences; +import android.icu.text.SimpleDateFormat; +import android.icu.util.TimeZone; import android.util.Log; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** Utils class to report hearing aid metrics to statsd */ public final class HearingAidStatsLogUtils { private static final String TAG = "HearingAidStatsLogUtils"; + private static final boolean DEBUG = true; + private static final String ACCESSIBILITY_PREFERENCE = "accessibility_prefs"; + private static final String BT_HEARING_AIDS_PAIRED_HISTORY = "bt_hearing_aids_paired_history"; + private static final String BT_HEARING_AIDS_CONNECTED_HISTORY = + "bt_hearing_aids_connected_history"; + private static final String BT_HEARING_DEVICES_PAIRED_HISTORY = + "bt_hearing_devices_paired_history"; + private static final String BT_HEARING_DEVICES_CONNECTED_HISTORY = + "bt_hearing_devices_connected_history"; + private static final String BT_HEARING_USER_CATEGORY = "bt_hearing_user_category"; + + private static final String HISTORY_RECORD_DELIMITER = ","; + private static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser"; + private static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser"; + private static final String CATEGORY_HEARING_DEVICES = "A11yHearingDevicesUser"; + private static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingDevicesUser"; + + private static final long PAIRED_HISTORY_EXPIRED_TIME = TimeUnit.DAYS.toMillis(30); + private static final long CONNECTED_HISTORY_EXPIRED_TIME = TimeUnit.DAYS.toMillis(7); + private static final int VALID_PAIRED_EVENT_COUNT = 1; + private static final int VALID_CONNECTED_EVENT_COUNT = 7; + + /** + * Type of different Bluetooth device events history related to hearing. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + HistoryType.TYPE_UNKNOWN, + HistoryType.TYPE_HEARING_AIDS_PAIRED, + HistoryType.TYPE_HEARING_AIDS_CONNECTED, + HistoryType.TYPE_HEARING_DEVICES_PAIRED, + HistoryType.TYPE_HEARING_DEVICES_CONNECTED}) + public @interface HistoryType { + int TYPE_UNKNOWN = -1; + int TYPE_HEARING_AIDS_PAIRED = 0; + int TYPE_HEARING_AIDS_CONNECTED = 1; + int TYPE_HEARING_DEVICES_PAIRED = 2; + int TYPE_HEARING_DEVICES_CONNECTED = 3; + } + private static final HashMap<String, Integer> sDeviceAddressToBondEntryMap = new HashMap<>(); + private static final Set<String> sJustBondedDeviceAddressSet = new HashSet<>(); /** * Sets the mapping from hearing aid device to the bond entry where this device starts it's @@ -69,5 +125,227 @@ public final class HearingAidStatsLogUtils { return sDeviceAddressToBondEntryMap; } + /** + * Indicates if user is categorized as one of {@link #CATEGORY_HEARING_AIDS}, + * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARING_DEVICES}, and + * {@link #CATEGORY_NEW_HEARING_DEVICES}. + * + * @param context the request context + * @return true if user is already categorized as one of interested group + */ + public static boolean isUserCategorized(Context context) { + String userCategory = getSharedPreferences(context).getString(BT_HEARING_USER_CATEGORY, ""); + return !userCategory.isEmpty(); + } + + /** + * Returns the user category if the user is already categorized. Otherwise, checks the + * history and sees if the user is categorized as one of {@link #CATEGORY_HEARING_AIDS}, + * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARING_DEVICES}, and + * {@link #CATEGORY_NEW_HEARING_DEVICES}. + * + * @param context the request context + * @return the category which user belongs to + */ + public static synchronized String getUserCategory(Context context) { + String userCategory = getSharedPreferences(context).getString(BT_HEARING_USER_CATEGORY, ""); + if (!userCategory.isEmpty()) { + return userCategory; + } + + LinkedList<Long> hearingAidsConnectedHistory = getHistory(context, + HistoryType.TYPE_HEARING_AIDS_CONNECTED); + if (hearingAidsConnectedHistory != null + && hearingAidsConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) { + LinkedList<Long> hearingAidsPairedHistory = getHistory(context, + HistoryType.TYPE_HEARING_AIDS_PAIRED); + // Since paired history will be cleared after 30 days. If there's any record within 30 + // days, the user will be categorized as CATEGORY_NEW_HEARING_AIDS. Otherwise, the user + // will be categorized as CATEGORY_HEARING_AIDS. + if (hearingAidsPairedHistory != null + && hearingAidsPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) { + userCategory = CATEGORY_NEW_HEARING_AIDS; + } else { + userCategory = CATEGORY_HEARING_AIDS; + } + } + + LinkedList<Long> hearingDevicesConnectedHistory = getHistory(context, + HistoryType.TYPE_HEARING_DEVICES_CONNECTED); + if (hearingDevicesConnectedHistory != null + && hearingDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) { + LinkedList<Long> hearingDevicesPairedHistory = getHistory(context, + HistoryType.TYPE_HEARING_DEVICES_PAIRED); + // Since paired history will be cleared after 30 days. If there's any record within 30 + // days, the user will be categorized as CATEGORY_NEW_HEARING_DEVICES. Otherwise, the + // user will be categorized as CATEGORY_HEARING_DEVICES. + if (hearingDevicesPairedHistory != null + && hearingDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) { + userCategory = CATEGORY_NEW_HEARING_DEVICES; + } else { + userCategory = CATEGORY_HEARING_DEVICES; + } + } + + if (!userCategory.isEmpty()) { + // History become useless once user is categorized. Clear all history. + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putString(BT_HEARING_USER_CATEGORY, userCategory).apply(); + clearHistory(context); + sJustBondedDeviceAddressSet.clear(); + } + return userCategory; + } + + /** + * Maintains a temporarily list of just bonded device address. After the device profiles are + * connected, {@link HearingAidStatsLogUtils#removeFromJustBonded} will be called to remove the + * address. + * @param address the device address + */ + public static void addToJustBonded(String address) { + sJustBondedDeviceAddressSet.add(address); + } + + /** + * Removes the device address from the just bonded list. + * @param address the device address + */ + public static void removeFromJustBonded(String address) { + sJustBondedDeviceAddressSet.remove(address); + } + + /** + * Checks whether the device address is in the just bonded list. + * @param address the device address + * @return true if the device address is in the just bonded list + */ + public static boolean isJustBonded(String address) { + return sJustBondedDeviceAddressSet.contains(address); + } + + /** + * Clears all BT hearing devices related history stored in shared preference. + * @param context the request context + */ + private static synchronized void clearHistory(Context context) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.remove(BT_HEARING_AIDS_PAIRED_HISTORY) + .remove(BT_HEARING_AIDS_CONNECTED_HISTORY) + .remove(BT_HEARING_DEVICES_PAIRED_HISTORY) + .remove(BT_HEARING_DEVICES_CONNECTED_HISTORY) + .apply(); + } + + /** + * Adds current timestamp into BT hearing devices related history. + * @param context the request context + * @param type the type of history to store the data. See {@link HistoryType}. + */ + public static void addCurrentTimeToHistory(Context context, @HistoryType int type) { + addToHistory(context, type, System.currentTimeMillis()); + } + + static synchronized void addToHistory(Context context, @HistoryType int type, + long timestamp) { + + LinkedList<Long> history = getHistory(context, type); + if (history == null) { + if (DEBUG) { + Log.w(TAG, "Couldn't find shared preference name matched type=" + type); + } + return; + } + if (history.peekLast() != null && isSameDay(history.peekLast(), timestamp)) { + if (DEBUG) { + Log.w(TAG, "Skip this record, it's same day record"); + } + return; + } + history.add(timestamp); + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putString(HISTORY_TYPE_TO_SP_NAME_MAPPING.get(type), + convertToHistoryString(history)).apply(); + } + + @Nullable + static synchronized LinkedList<Long> getHistory(Context context, @HistoryType int type) { + String spName = HISTORY_TYPE_TO_SP_NAME_MAPPING.get(type); + if (BT_HEARING_AIDS_PAIRED_HISTORY.equals(spName) + || BT_HEARING_DEVICES_PAIRED_HISTORY.equals(spName)) { + LinkedList<Long> history = convertToHistoryList( + getSharedPreferences(context).getString(spName, "")); + removeRecordsBeforeTime(history, PAIRED_HISTORY_EXPIRED_TIME); + return history; + } else if (BT_HEARING_AIDS_CONNECTED_HISTORY.equals(spName) + || BT_HEARING_DEVICES_CONNECTED_HISTORY.equals(spName)) { + LinkedList<Long> history = convertToHistoryList( + getSharedPreferences(context).getString(spName, "")); + removeRecordsBeforeTime(history, CONNECTED_HISTORY_EXPIRED_TIME); + return history; + } + return null; + } + + private static void removeRecordsBeforeTime(LinkedList<Long> history, long time) { + if (history == null) { + return; + } + Long currentTime = System.currentTimeMillis(); + while (history.peekFirst() != null + && currentTime - history.peekFirst() > time) { + history.poll(); + } + } + + private static String convertToHistoryString(LinkedList<Long> history) { + return history.stream().map(Object::toString).collect( + Collectors.joining(HISTORY_RECORD_DELIMITER)); + } + private static LinkedList<Long> convertToHistoryList(String string) { + if (string == null || string.isEmpty()) { + return new LinkedList<>(); + } + LinkedList<Long> ll = new LinkedList<>(); + String[] elements = string.split(HISTORY_RECORD_DELIMITER); + for (String e: elements) { + if (e.isEmpty()) continue; + ll.offer(Long.parseLong(e)); + } + return ll; + } + + /** + * Check if two timestamps are in the same date according to current timezone. This function + * doesn't consider the original timezone when the timestamp is saved. + * + * @param t1 the first epoch timestamp + * @param t2 the second epoch timestamp + * @return {@code true} if two timestamps are on the same day + */ + private static boolean isSameDay(long t1, long t2) { + final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); + sdf.setTimeZone(TimeZone.getDefault()); + String dateString1 = sdf.format(t1); + String dateString2 = sdf.format(t2); + return dateString1.equals(dateString2); + } + + private static SharedPreferences getSharedPreferences(Context context) { + return context.getSharedPreferences(ACCESSIBILITY_PREFERENCE, Context.MODE_PRIVATE); + } + + private static final HashMap<Integer, String> HISTORY_TYPE_TO_SP_NAME_MAPPING; + static { + HISTORY_TYPE_TO_SP_NAME_MAPPING = new HashMap<>(); + HISTORY_TYPE_TO_SP_NAME_MAPPING.put( + HistoryType.TYPE_HEARING_AIDS_PAIRED, BT_HEARING_AIDS_PAIRED_HISTORY); + HISTORY_TYPE_TO_SP_NAME_MAPPING.put( + HistoryType.TYPE_HEARING_AIDS_CONNECTED, BT_HEARING_AIDS_CONNECTED_HISTORY); + HISTORY_TYPE_TO_SP_NAME_MAPPING.put( + HistoryType.TYPE_HEARING_DEVICES_PAIRED, BT_HEARING_DEVICES_PAIRED_HISTORY); + HISTORY_TYPE_TO_SP_NAME_MAPPING.put( + HistoryType.TYPE_HEARING_DEVICES_CONNECTED, BT_HEARING_DEVICES_CONNECTED_HISTORY); + } private HearingAidStatsLogUtils() {} } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java index 0cf5b8900245..8a75bdc7bc35 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java @@ -20,6 +20,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + import com.android.internal.util.FrameworkStatsLog; import org.junit.Rule; @@ -31,15 +35,21 @@ import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import java.util.HashMap; +import java.util.LinkedList; +import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) public class HearingAidStatsLogUtilsTest { private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; + private static final int TEST_HISTORY_TYPE = + HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED; @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private CachedBluetoothDevice mCachedBluetoothDevice; @@ -71,4 +81,44 @@ public class HearingAidStatsLogUtilsTest { HearingAidStatsLogUtils.getDeviceAddressToBondEntryMap(); assertThat(map.containsKey(TEST_DEVICE_ADDRESS)).isFalse(); } + + @Test + public void addCurrentTimeToHistory_addNewData() { + final long currentTime = System.currentTimeMillis(); + final long lastData = currentTime - TimeUnit.DAYS.toMillis(2); + HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, lastData); + + HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE); + + LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, TEST_HISTORY_TYPE); + assertThat(history).isNotNull(); + assertThat(history.size()).isEqualTo(2); + } + @Test + public void addCurrentTimeToHistory_skipSameDateData() { + final long currentTime = System.currentTimeMillis(); + final long lastData = currentTime - 1; + HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, lastData); + + HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE); + + LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, TEST_HISTORY_TYPE); + assertThat(history).isNotNull(); + assertThat(history.size()).isEqualTo(1); + assertThat(history.getFirst()).isEqualTo(lastData); + } + + @Test + public void addCurrentTimeToHistory_cleanUpExpiredData() { + final long currentTime = System.currentTimeMillis(); + final long expiredData = currentTime - TimeUnit.DAYS.toMillis(10); + HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, expiredData); + + HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE); + + LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, TEST_HISTORY_TYPE); + assertThat(history).isNotNull(); + assertThat(history.size()).isEqualTo(1); + assertThat(history.getFirst()).isNotEqualTo(expiredData); + } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 80cf6c313316..20740dcedf95 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -223,6 +223,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR); VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.WEAR_TTS_PREWARM_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF, BOOLEAN_VALIDATOR); VALIDATORS.put(System.UNREAD_NOTIFICATION_DOT_INDICATOR, BOOLEAN_VALIDATOR); VALIDATORS.put(System.AUTO_LAUNCH_MEDIA_CONTROLS, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c697c1ff66ec..203efbf76658 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -103,6 +103,7 @@ public class SettingsBackupTest { Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities Settings.System.SCREEN_BRIGHTNESS_FLOAT, Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, + Settings.System.WEAR_TTS_PREWARM_ENABLED, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific ); diff --git a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt index 92d752c83a9f..4837aad3a025 100644 --- a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt +++ b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt @@ -88,6 +88,7 @@ internal object StatementUtils { } catch (e: Exception) { return Result.Failure(e) } + checkNotNull(signingInfo) return if (signingInfo.hasMultipleSigners()) { signingInfo.apkContentsSigners } else { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index da48762e1960..0a100babde75 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -14,26 +14,20 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.composable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import android.view.View +import android.view.ViewGroup import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.SceneScope -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.qualifiers.KeyguardRootView import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -42,6 +36,7 @@ import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -54,6 +49,7 @@ class LockscreenScene constructor( @Application private val applicationScope: CoroutineScope, private val viewModel: LockscreenSceneViewModel, + @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View, ) : ComposableScene { override val key = SceneKey.Lockscreen @@ -72,6 +68,7 @@ constructor( ) { LockscreenScene( viewModel = viewModel, + viewProvider = viewProvider, modifier = modifier, ) } @@ -89,25 +86,22 @@ constructor( @Composable private fun LockscreenScene( viewModel: LockscreenSceneViewModel, + viewProvider: () -> View, modifier: Modifier = Modifier, ) { - // TODO(b/280879610): implement the real UI. - - val lockButtonIcon: Icon by viewModel.lockButtonIcon.collectAsState() - - Box(modifier = modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.align(Alignment.Center) - ) { - Text("Lockscreen", style = MaterialTheme.typography.headlineMedium) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button(onClick = { viewModel.onLockButtonClicked() }) { Icon(lockButtonIcon) } - - Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") } + AndroidView( + factory = { _ -> + val keyguardRootView = viewProvider() + // Remove the KeyguardRootView from any parent it might already have in legacy code just + // in case (a view can't have two parents). + (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView) + keyguardRootView + }, + update = { keyguardRootView -> + keyguardRootView.requireViewById<View>(R.id.lock_icon_view).setOnClickListener { + viewModel.onLockButtonClicked() } - } - } + }, + modifier = modifier, + ) } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index ab0225d63ac5..966e183fc821 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -51,15 +51,18 @@ import kotlinx.coroutines.withContext private val KEY_TIMESTAMP = "appliedTimestamp" private val KNOWN_PLUGINS = mapOf<String, List<ClockMetadata>>( - "com.android.systemui.falcon.one" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")), - "com.android.systemui.falcon.two" to listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")), - "com.android.systemui.falcon.three" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")), - "com.android.systemui.falcon.four" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")), - "com.android.systemui.falcon.five" to listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")), - "com.android.systemui.falcon.six" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")), - "com.android.systemui.falcon.seven" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")), - "com.android.systemui.falcon.eight" to listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")), - "com.android.systemui.falcon.nine" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")), + "com.android.systemui.clocks.bignum" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")), + "com.android.systemui.clocks.calligraphy" to + listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")), + "com.android.systemui.clocks.flex" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")), + "com.android.systemui.clocks.growth" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")), + "com.android.systemui.clocks.handwritten" to + listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")), + "com.android.systemui.clocks.inflate" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")), + "com.android.systemui.clocks.metro" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")), + "com.android.systemui.clocks.numoverlap" to + listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")), + "com.android.systemui.clocks.weather" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")), ) private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut( diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 92083b0ba1b9..785177ed0f51 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -292,6 +292,7 @@ -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt -packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt -packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt -packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt diff --git a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml new file mode 100644 index 000000000000..a8abd793bd00 --- /dev/null +++ b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" /> + <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" /> + <item android:color="@color/transparent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml index a8c034986425..47a2965bcfac 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml @@ -32,6 +32,12 @@ <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="@color/qs_footer_power_button_overlay_color"/> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> + </shape> + </item> </ripple> </inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_pin_content_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_pin_content_view.xml new file mode 100644 index 000000000000..24222f7642be --- /dev/null +++ b/packages/SystemUI/res/layout-land/auth_credential_password_pin_content_view.xml @@ -0,0 +1,101 @@ +<!-- + ~ 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. + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <RelativeLayout + android:id="@+id/auth_credential_header" + style="?headerStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"> + + <ImageView + android:id="@+id/icon" + style="?headerIconStyle" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:contentDescription="@null"/> + + <TextView + android:id="@+id/title" + style="?titleTextAppearance" + android:layout_below="@id/icon" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/subtitle" + style="?subTitleTextAppearance" + android:layout_below="@id/title" + android:layout_alignParentLeft="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/description" + style="?descriptionTextAppearance" + android:layout_below="@id/subtitle" + android:layout_alignParentLeft="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </RelativeLayout> + + <FrameLayout + android:id="@+id/auth_credential_input" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical"> + + <ImeAwareEditText + android:id="@+id/lockPassword" + style="?passwordTextAppearance" + android:layout_width="208dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" + android:inputType="textPassword" + android:minHeight="48dp"/> + + <TextView + android:id="@+id/error" + style="?errorTextAppearance" + android:layout_gravity="center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout> + + <Button + android:id="@+id/emergencyCallButton" + style="@style/AuthCredentialEmergencyButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:text="@string/work_challenge_emergency_button_text"/> + </FrameLayout> + +</merge> diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml index e439f775f8ea..8ac7583088f9 100644 --- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml @@ -23,84 +23,6 @@ android:elevation="@dimen/biometric_dialog_elevation" android:theme="?app:attr/lockPinPasswordStyle"> - <RelativeLayout - android:id="@+id/auth_credential_header" - style="?headerStyle" - android:layout_width="wrap_content" - android:layout_height="match_parent"> - - <ImageView - android:id="@+id/icon" - style="?headerIconStyle" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:contentDescription="@null"/> - - <TextView - android:id="@+id/title" - style="?titleTextAppearance" - android:layout_below="@id/icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - <TextView - android:id="@+id/subtitle" - style="?subTitleTextAppearance" - android:layout_below="@id/title" - android:layout_alignParentLeft="true" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - <TextView - android:id="@+id/description" - style="?descriptionTextAppearance" - android:layout_below="@id/subtitle" - android:layout_alignParentLeft="true" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - </RelativeLayout> - - <FrameLayout - android:id="@+id/auth_credential_input" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal|top" - android:orientation="vertical"> - - <ImeAwareEditText - android:id="@+id/lockPassword" - style="?passwordTextAppearance" - android:layout_width="208dp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" - android:inputType="textPassword" - android:minHeight="48dp"/> - - <TextView - android:id="@+id/error" - style="?errorTextAppearance" - android:layout_gravity="center_horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> - </LinearLayout> - - <Button - android:id="@+id/emergencyCallButton" - style="@style/AuthCredentialEmergencyButtonStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layout_gravity="center_horizontal|bottom" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:text="@string/work_challenge_emergency_button_text"/> - </FrameLayout> + <include layout="@layout/auth_credential_password_pin_content_view" /> </com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/auth_credential_pin_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pin_view.xml new file mode 100644 index 000000000000..8ac7583088f9 --- /dev/null +++ b/packages/SystemUI/res/layout-land/auth_credential_pin_view.xml @@ -0,0 +1,28 @@ +<!-- + ~ 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. + --> + +<com.android.systemui.biometrics.ui.CredentialPasswordView + 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" + android:orientation="horizontal" + android:elevation="@dimen/biometric_dialog_elevation" + android:theme="?app:attr/lockPinPasswordStyle"> + + <include layout="@layout/auth_credential_password_pin_content_view" /> + +</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_password_pin_content_view.xml b/packages/SystemUI/res/layout/auth_credential_password_pin_content_view.xml new file mode 100644 index 000000000000..11284fd2237b --- /dev/null +++ b/packages/SystemUI/res/layout/auth_credential_password_pin_content_view.xml @@ -0,0 +1,104 @@ +<!-- + ~ 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. + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <RelativeLayout + android:id="@+id/auth_credential_header" + style="?headerStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="0dp"> + + <ImageView + android:id="@+id/icon" + style="?headerIconStyle" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:contentDescription="@null" /> + + <TextView + android:id="@+id/title" + style="?titleTextAppearance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/icon" /> + + <TextView + android:id="@+id/subtitle" + style="?subTitleTextAppearance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/title" /> + + <TextView + android:id="@+id/description" + style="?descriptionTextAppearance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/subtitle" /> + + </RelativeLayout> + + </ScrollView> + + <FrameLayout + android:id="@+id/auth_credential_input" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical"> + + <ImeAwareEditText + android:id="@+id/lockPassword" + style="?passwordTextAppearance" + android:layout_width="208dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" + android:inputType="textPassword" + android:minHeight="48dp"/> + + <TextView + android:id="@+id/error" + style="?errorTextAppearance" + android:layout_gravity="center_horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </LinearLayout> + + <Button + android:id="@+id/emergencyCallButton" + style="@style/AuthCredentialEmergencyButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:text="@string/work_challenge_emergency_button_text"/> + </FrameLayout> + +</merge> diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml index 9336845f20f7..f8d9a87d5e54 100644 --- a/packages/SystemUI/res/layout/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -23,88 +23,6 @@ android:orientation="vertical" android:theme="?app:attr/lockPinPasswordStyle"> - <ScrollView - android:id="@+id/auth_credential_header" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <RelativeLayout - style="?headerStyle" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <ImageView - android:id="@+id/icon" - style="?headerIconStyle" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:contentDescription="@null" /> - - <TextView - android:id="@+id/title" - style="?titleTextAppearance" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/icon" /> - - <TextView - android:id="@+id/subtitle" - style="?subTitleTextAppearance" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/title" /> - - <TextView - android:id="@+id/description" - style="?descriptionTextAppearance" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/subtitle" /> - - </RelativeLayout> - - </ScrollView> - - <FrameLayout - android:id="@+id/auth_credential_input" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal|top" - android:orientation="vertical"> - - <ImeAwareEditText - android:id="@+id/lockPassword" - style="?passwordTextAppearance" - android:layout_width="208dp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" - android:inputType="textPassword" - android:minHeight="48dp"/> - - <TextView - android:id="@+id/error" - style="?errorTextAppearance" - android:layout_gravity="center_horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - </LinearLayout> - - <Button - android:id="@+id/emergencyCallButton" - style="@style/AuthCredentialEmergencyButtonStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layout_gravity="center_horizontal|bottom" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:text="@string/work_challenge_emergency_button_text"/> - </FrameLayout> + <include layout="@layout/auth_credential_password_pin_content_view" /> </com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_pin_view.xml b/packages/SystemUI/res/layout/auth_credential_pin_view.xml new file mode 100644 index 000000000000..a1cf807af088 --- /dev/null +++ b/packages/SystemUI/res/layout/auth_credential_pin_view.xml @@ -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. + --> + +<com.android.systemui.biometrics.ui.CredentialPasswordView + 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" + android:elevation="@dimen/biometric_dialog_elevation" + android:orientation="vertical" + android:theme="?app:attr/lockPinPasswordStyle"> + + <include layout="@layout/auth_credential_password_pin_content_view" /> + +</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml index 66c215599949..1e5b249b5021 100644 --- a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml +++ b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml @@ -13,15 +13,32 @@ See the License for the specific language governing permissions and limitations under the License. --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@android:id/text1" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="?androidprv:attr/textColorOnAccent" - android:singleLine="true" android:layout_width="match_parent" - android:layout_height="@dimen/screenrecord_spinner_height" - android:gravity="center_vertical" - android:ellipsize="marquee" + android:layout_height="wrap_content" + android:minHeight="@dimen/screenrecord_spinner_height" + android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end" android:paddingStart="@dimen/screenrecord_spinner_text_padding_start" - android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end"/>
\ No newline at end of file + android:gravity="center_vertical" + android:orientation="vertical"> + + <TextView + android:id="@android:id/text1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?androidprv:attr/textColorOnAccent" /> + + <TextView + android:id="@android:id/text2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?androidprv:attr/colorError" /> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index fd1de25e8174..04eae64013b3 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -223,6 +223,9 @@ <!-- Communal mode --> <item type="id" name="communal_widget_wrapper" /> + <!-- Values assigned to the views in Biometrics Prompt --> + <item type="id" name="pin_pad"/> + <!-- Used to tag views programmatically added to the smartspace area so they can be more easily removed later. diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 77ffe193c98f..b37aeeecea61 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1107,6 +1107,8 @@ <string name="media_projection_entry_app_permission_dialog_warning_single_app">When you’re sharing, recording, or casting an app, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string> <!-- 1P/3P apps media projection permission button to continue with app selection or recording [CHAR LIMIT=60] --> <string name="media_projection_entry_app_permission_dialog_continue">Start</string> + <!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] --> + <string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string> <!-- Casting that launched by SysUI (i.e. when there is no app name) --> <!-- System casting media projection permission dialog title. [CHAR LIMIT=100] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 4bc949116807..33e453cf4354 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -76,26 +76,11 @@ oneway interface IOverviewProxy { void onSystemBarAttributesChanged(int displayId, int behavior) = 20; /** - * Sent when screen turned on and ready to use (blocker scrim is hidden) - */ - void onScreenTurnedOn() = 21; - - /** * Sent when the desired dark intensity of the nav buttons has changed */ void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22; /** - * Sent when screen started turning on. - */ - void onScreenTurningOn() = 23; - - /** - * Sent when screen started turning off. - */ - void onScreenTurningOff() = 24; - - /** * Sent when split keyboard shortcut is triggered to enter stage split. */ void enterStageSplitFromRunningApp(boolean leftOrTop) = 25; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index cab54d08b3ec..8200e5c84186 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -39,6 +39,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; +import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import android.view.HapticFeedbackConstants; @@ -76,6 +77,8 @@ public class RotationButtonController { private static final String TAG = "RotationButtonController"; private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000; + private static final boolean OEM_DISALLOW_ROTATION_IN_SUW = + SystemProperties.getBoolean("ro.setupwizard.rotation_locked", false); private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3; @@ -375,6 +378,12 @@ public class RotationButtonController { } public void onRotationProposal(int rotation, boolean isValid) { + boolean isUserSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; + if (!isUserSetupComplete && OEM_DISALLOW_ROTATION_IN_SUW) { + return; + } + int windowRotation = mWindowRotationProvider.get(); if (!mRotationButton.acceptRotationProposal()) { @@ -497,8 +506,7 @@ public class RotationButtonController { boolean canShowRotationButton() { return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT - || isGesturalMode(mNavBarMode) - || mTaskBarVisible; + || isGesturalMode(mNavBarMode); } @DrawableRes diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt index 899cad89a0be..006974c9fa92 100644 --- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt +++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt @@ -66,7 +66,7 @@ constructor( window.isNavigationBarContrastEnforced = false window.navigationBarColor = Color.TRANSPARENT - clock = findViewById(R.id.clock) + clock = requireViewById(R.id.clock) keyguardStatusViewController = keyguardStatusViewComponentFactory.build(clock).keyguardStatusViewController.apply { setDisplayedOnSecondaryDisplay() diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 802a550c4d29..7464c8803c99 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -499,6 +499,8 @@ public class AuthContainerView extends LinearLayout R.layout.auth_credential_pattern_view, null, false); break; case Utils.CREDENTIAL_PIN: + mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false); + break; case Utils.CREDENTIAL_PASSWORD: mCredentialView = factory.inflate( R.layout.auth_credential_password_view, null, false); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt index 20c3e4098e83..f7f910391566 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt @@ -56,10 +56,10 @@ interface UdfpsModule { ) ) } else { - BoundingBoxOverlapDetector() + BoundingBoxOverlapDetector(values[2]) } } else { - return BoundingBoxOverlapDetector() + return BoundingBoxOverlapDetector(1f) } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt index d2cb84945252..5b0bd959d902 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt @@ -20,7 +20,10 @@ package com.android.systemui.biometrics.data.repository import android.hardware.face.FaceManager import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.face.IFaceAuthenticatorsRegisteredCallback +import android.util.Log +import com.android.systemui.biometrics.shared.model.LockoutMode import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.biometrics.shared.model.toLockoutMode import com.android.systemui.biometrics.shared.model.toSensorStrength import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow @@ -29,16 +32,18 @@ import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn /** A repository for the global state of Face sensor. */ interface FacePropertyRepository { /** Face sensor information, null if it is not available. */ - val sensorInfo: Flow<FaceSensorInfo?> + val sensorInfo: StateFlow<FaceSensorInfo?> + + /** Get the current lockout mode for the user. This makes a binder based service call. */ + suspend fun getLockoutMode(userId: Int): LockoutMode } /** Describes a biometric sensor */ @@ -49,33 +54,39 @@ private const val TAG = "FaceSensorPropertyRepositoryImpl" @SysUISingleton class FacePropertyRepositoryImpl @Inject -constructor(@Application private val applicationScope: CoroutineScope, faceManager: FaceManager?) : - FacePropertyRepository { +constructor( + @Application private val applicationScope: CoroutineScope, + private val faceManager: FaceManager? +) : FacePropertyRepository { - private val sensorProps: Flow<List<FaceSensorPropertiesInternal>> = - faceManager?.let { - ConflatedCallbackFlow.conflatedCallbackFlow { - val callback = - object : IFaceAuthenticatorsRegisteredCallback.Stub() { - override fun onAllAuthenticatorsRegistered( - sensors: List<FaceSensorPropertiesInternal> - ) { - trySendWithFailureLogging( - sensors, - TAG, - "onAllAuthenticatorsRegistered" - ) - } + override val sensorInfo: StateFlow<FaceSensorInfo?> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val callback = + object : IFaceAuthenticatorsRegisteredCallback.Stub() { + override fun onAllAuthenticatorsRegistered( + sensors: List<FaceSensorPropertiesInternal>, + ) { + if (sensors.isEmpty()) return + trySendWithFailureLogging( + FaceSensorInfo( + sensors.first().sensorId, + sensors.first().sensorStrength.toSensorStrength() + ), + TAG, + "onAllAuthenticatorsRegistered" + ) } - it.addAuthenticatorsRegisteredCallback(callback) - awaitClose {} - } - .shareIn(applicationScope, SharingStarted.Eagerly) - } - ?: flowOf(emptyList()) + } + faceManager?.addAuthenticatorsRegisteredCallback(callback) + awaitClose {} + } + .onEach { Log.d(TAG, "sensorProps changed: $it") } + .stateIn(applicationScope, SharingStarted.Eagerly, null) - override val sensorInfo: Flow<FaceSensorInfo?> = - sensorProps - .map { it.firstOrNull() } - .map { it?.let { FaceSensorInfo(it.sensorId, it.sensorStrength.toSensorStrength()) } } + override suspend fun getLockoutMode(userId: Int): LockoutMode { + if (sensorInfo.value == null || faceManager == null) { + return LockoutMode.NONE + } + return faceManager.getLockoutModeForUser(sensorInfo.value!!.id, userId).toLockoutMode() + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LockoutMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LockoutMode.kt new file mode 100644 index 000000000000..68bba32537b0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LockoutMode.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.shared.model + +import android.hardware.biometrics.BiometricConstants + +/** Lockout mode. Represents [BiometricConstants.LockoutMode]. */ +enum class LockoutMode { + NONE, + TIMED, + PERMANENT, +} + +/** Convert [this] to corresponding [LockoutMode] */ +fun Int.toLockoutMode(): LockoutMode = + when (this) { + BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT -> LockoutMode.PERMANENT + BiometricConstants.BIOMETRIC_LOCKOUT_TIMED -> LockoutMode.TIMED + else -> LockoutMode.NONE + } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt index cf6044f146b0..9b946db0daf0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt @@ -17,16 +17,30 @@ package com.android.systemui.biometrics.udfps import android.graphics.Rect +import android.os.Build +import android.util.Log import com.android.systemui.dagger.SysUISingleton /** Returns whether the touch coordinates are within the sensor's bounding box. */ @SysUISingleton -class BoundingBoxOverlapDetector : OverlapDetector { +class BoundingBoxOverlapDetector(private val targetSize: Float) : OverlapDetector { + + private val TAG = "BoundingBoxOverlapDetector" + override fun isGoodOverlap( touchData: NormalizedTouchData, nativeSensorBounds: Rect, nativeOverlayBounds: Rect, - ): Boolean = - touchData.isWithinBounds(nativeOverlayBounds) && - touchData.isWithinBounds(nativeSensorBounds) + ): Boolean { + val scaledRadius = (nativeSensorBounds.width() / 2) * targetSize + val scaledSensorBounds = + Rect( + (nativeSensorBounds.centerX() - scaledRadius).toInt(), + (nativeSensorBounds.centerY() - scaledRadius).toInt(), + (nativeSensorBounds.centerX() + scaledRadius).toInt(), + (nativeSensorBounds.centerY() + scaledRadius).toInt(), + ) + + return touchData.isWithinBounds(scaledSensorBounds) + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt index 709fe855fbfc..6c5cc4835787 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt @@ -8,11 +8,8 @@ import android.view.View import android.view.WindowInsets import android.view.WindowInsets.Type.ime import android.view.accessibility.AccessibilityManager -import android.widget.ImageView -import android.widget.ImeAwareEditText import android.widget.LinearLayout import android.widget.TextView -import androidx.core.view.isGone import com.android.systemui.R import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.ui.binder.CredentialViewBinder @@ -22,14 +19,6 @@ import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel class CredentialPasswordView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs), CredentialView, View.OnApplyWindowInsetsListener { - private lateinit var titleView: TextView - private lateinit var subtitleView: TextView - private lateinit var descriptionView: TextView - private lateinit var iconView: ImageView - private lateinit var passwordField: ImeAwareEditText - private lateinit var credentialHeader: View - private lateinit var credentialInput: View - private var bottomInset: Int = 0 private val accessibilityManager by lazy { @@ -48,90 +37,32 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) : override fun onFinishInflate() { super.onFinishInflate() - - titleView = requireViewById(R.id.title) - subtitleView = requireViewById(R.id.subtitle) - descriptionView = requireViewById(R.id.description) - iconView = requireViewById(R.id.icon) - passwordField = requireViewById(R.id.lockPassword) - credentialHeader = requireViewById(R.id.auth_credential_header) - credentialInput = requireViewById(R.id.auth_credential_input) - setOnApplyWindowInsetsListener(this) } - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - super.onLayout(changed, left, top, right, bottom) - - val inputLeftBound: Int - var inputTopBound: Int - var headerRightBound = right - var headerTopBounds = top - var headerBottomBounds = bottom - val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom - val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom - if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) { - inputTopBound = (bottom - credentialInput.height) / 2 - inputLeftBound = (right - left) / 2 - headerRightBound = inputLeftBound - if (descriptionView.bottom > headerBottomBounds) { - headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset) - credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom) - } - } else { - inputTopBound = descBottom + (bottom - descBottom - credentialInput.height) / 2 - inputLeftBound = (right - left - credentialInput.width) / 2 - - if (bottom - inputTopBound < credentialInput.height) { - inputTopBound = bottom - credentialInput.height - } - - if (descriptionView.bottom > inputTopBound) { - credentialHeader.layout(left, headerTopBounds, headerRightBound, inputTopBound) - } - } - - credentialInput.layout(inputLeftBound, inputTopBound, right, bottom) - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - val newWidth = MeasureSpec.getSize(widthMeasureSpec) - val newHeight = MeasureSpec.getSize(heightMeasureSpec) - bottomInset - - setMeasuredDimension(newWidth, newHeight) - - val halfWidthSpec = MeasureSpec.makeMeasureSpec(width / 2, MeasureSpec.AT_MOST) - val fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED) - if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) { - measureChildren(halfWidthSpec, fullHeightSpec) - } else { - measureChildren(widthMeasureSpec, fullHeightSpec) - } - } - override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { - val bottomInsets = insets.getInsets(ime()) - if (bottomInset != bottomInsets.bottom) { - bottomInset = bottomInsets.bottom - - if (bottomInset > 0 && resources.configuration.orientation == ORIENTATION_LANDSCAPE) { - titleView.isSingleLine = true - titleView.ellipsize = TextUtils.TruncateAt.MARQUEE - titleView.marqueeRepeatLimit = -1 - // select to enable marquee unless a screen reader is enabled - titleView.isSelected = accessibilityManager?.shouldMarquee() ?: false - } else { - titleView.isSingleLine = false - titleView.ellipsize = null - // select to enable marquee unless a screen reader is enabled - titleView.isSelected = false + val imeBottomInset = insets.getInsets(ime()).bottom + if (bottomInset != imeBottomInset) { + val titleView: TextView? = findViewById(R.id.title) + if (titleView != null) { + if ( + bottomInset > 0 && resources.configuration.orientation == ORIENTATION_LANDSCAPE + ) { + titleView.isSingleLine = true + titleView.ellipsize = TextUtils.TruncateAt.MARQUEE + titleView.marqueeRepeatLimit = -1 + // select to enable marquee unless a screen reader is enabled + titleView.isSelected = accessibilityManager.shouldMarquee() + } else { + titleView.isSingleLine = false + titleView.ellipsize = null + // select to enable marquee unless a screen reader is enabled + titleView.isSelected = false + } } - - requestLayout() } - return insets + setPadding(paddingLeft, paddingTop, paddingRight, imeBottomInset) + return insets.inset(0, 0, 0, imeBottomInset) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/IPinPad.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/IPinPad.kt new file mode 100644 index 000000000000..cf6865c0fe8e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/IPinPad.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui + +/** + * Interface for PinPad in auth_credential_pin_view. This is needed when a custom pin pad is + * preferred to the IME To use a PinPad, one needs to implement IPinPad interface and provide it in + * auth_credential_pin_view and specify the id as [pin_pad] + */ +interface IPinPad { + fun setPinPadClickListener(pinPadClickListener: PinPadClickListener) +} + +/** The call back interface for onClick event in the view. */ +interface PinPadClickListener { + /** + * One of the digit key has been clicked. + * + * @param digit A String representing a digit between 0 and 9. + */ + fun onDigitKeyClick(digit: String?) + + /** The backspace key has been clicked. */ + fun onBackspaceClick() + + /** The enter key has been clicked. */ + fun onEnterKeyClick() +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt index c27d71522c2f..996b62e084cb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt @@ -15,6 +15,7 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.R import com.android.systemui.biometrics.ui.CredentialPasswordView import com.android.systemui.biometrics.ui.CredentialView +import com.android.systemui.biometrics.ui.IPinPad import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.awaitCancellation @@ -53,13 +54,19 @@ object CredentialPasswordViewBinder { } ) passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback)) - + val pinPadView = view.findViewById(R.id.pin_pad) as? IPinPad + if (pinPadView != null) { + PinPadViewBinder.bind(pinPadView, view) + } repeatOnLifecycle(Lifecycle.State.STARTED) { // dismiss on a valid credential check launch { viewModel.validatedAttestation.collect { attestation -> if (attestation != null) { - imeManager.hideSoftInputFromWindow(view.windowToken, 0 /* flags */) + imeManager.hideSoftInputFromWindow( + view.windowToken, + 0 // flag + ) host.onCredentialMatched(attestation) } else { passwordField.setText("") diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt index 4ac9f967920f..25fe61916644 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt @@ -47,6 +47,7 @@ object CredentialViewBinder { val descriptionView: TextView = view.requireViewById(R.id.description) val iconView: ImageView? = view.findViewById(R.id.icon) val errorView: TextView = view.requireViewById(R.id.error) + val cancelButton: Button? = view.findViewById(R.id.cancel_button) val emergencyButtonView: Button = view.requireViewById(R.id.emergencyCallButton) var errorTimer: Job? = null @@ -60,7 +61,7 @@ object CredentialViewBinder { updateForContentDimensions( containerWidth, containerHeight, - 0 /* animateDurationMs */ + 0 // animateDurationMs ) } } @@ -103,7 +104,18 @@ object CredentialViewBinder { } } } - .collect { errorView.textOrHide = it } + .collect { it -> + val hasError = !it.isNullOrBlank() + errorView.visibility = + if (hasError) { + View.VISIBLE + } else if (cancelButton != null) { + View.INVISIBLE + } else { + View.GONE + } + errorView.text = if (hasError) it else "" + } } // show an extra dialog if the remaining attempts becomes low @@ -117,6 +129,8 @@ object CredentialViewBinder { } } + cancelButton?.setOnClickListener { host.onCredentialAborted() } + // bind the auth widget when (view) { is CredentialPasswordView -> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PinPadViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PinPadViewBinder.kt new file mode 100644 index 000000000000..906206c27455 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PinPadViewBinder.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.ui.binder + +import android.view.KeyEvent +import android.widget.ImeAwareEditText +import com.android.internal.widget.LockscreenCredential +import com.android.systemui.R +import com.android.systemui.biometrics.ui.CredentialPasswordView +import com.android.systemui.biometrics.ui.IPinPad +import com.android.systemui.biometrics.ui.PinPadClickListener + +/** Binder for IPinPad */ +object PinPadViewBinder { + /** Implements a PinPadClickListener inside a pin pad */ + @JvmStatic + fun bind(view: IPinPad, credentialPasswordView: CredentialPasswordView) { + val passwordField: ImeAwareEditText = + credentialPasswordView.requireViewById(R.id.lockPassword) + view.setPinPadClickListener( + object : PinPadClickListener { + + override fun onDigitKeyClick(digit: String?) { + passwordField.append(digit) + } + + override fun onBackspaceClick() { + val pin = LockscreenCredential.createPinOrNone(passwordField.text) + if (pin.size() > 0) { + passwordField.text.delete( + passwordField.selectionEnd - 1, + passwordField.selectionEnd + ) + } + pin.zeroize() + } + + override fun onEnterKeyClick() { + passwordField.dispatchKeyEvent( + KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0) + ) + passwordField.dispatchKeyEvent( + KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0) + ) + } + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 4dc7720ef447..e8b8f54220aa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -289,7 +289,7 @@ constructor( if (authenticateAfterError) { showAuthenticating(messageAfterError) } else { - showHelp(messageAfterError) + showInfo(messageAfterError) } } } @@ -309,12 +309,15 @@ constructor( private fun supportsRetry(failedModality: BiometricModality) = failedModality == BiometricModality.Face + suspend fun showHelp(message: String) = showHelp(message, clearIconError = false) + suspend fun showInfo(message: String) = showHelp(message, clearIconError = true) + /** * Show a persistent help message. * * Will be show even if the user has already authenticated. */ - suspend fun showHelp(message: String) { + private suspend fun showHelp(message: String, clearIconError: Boolean) { val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated if (!alreadyAuthenticated) { _isAuthenticating.value = false @@ -329,6 +332,8 @@ constructor( AuthBiometricView.STATE_PENDING_CONFIRMATION } else if (alreadyAuthenticated && !isConfirmationRequired.first()) { AuthBiometricView.STATE_AUTHENTICATED + } else if (clearIconError) { + AuthBiometricView.STATE_IDLE } else { AuthBiometricView.STATE_HELP } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt index 918e1680fc66..f2b4e09c9850 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt @@ -58,7 +58,7 @@ interface KeyguardBouncerRepository { * ``` */ val panelExpansionAmount: StateFlow<Float> - val keyguardPosition: StateFlow<Float> + val keyguardPosition: StateFlow<Float?> val isBackButtonEnabled: StateFlow<Boolean?> /** Determines if user is already unlocked */ val keyguardAuthenticated: StateFlow<Boolean?> @@ -130,7 +130,7 @@ constructor( */ private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN) override val panelExpansionAmount = _panelExpansionAmount.asStateFlow() - private val _keyguardPosition = MutableStateFlow(0f) + private val _keyguardPosition = MutableStateFlow<Float?>(null) override val keyguardPosition = _keyguardPosition.asStateFlow() private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null) override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() @@ -244,6 +244,7 @@ constructor( .logDiffsForTable(buffer, "", "PanelExpansionAmountMillis", -1) .launchIn(applicationScope) keyguardPosition + .filterNotNull() .map { it.toInt() } .logDiffsForTable(buffer, "", "KeyguardPosition", -1) .launchIn(applicationScope) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index c48660328bf0..0e0f1f6265d7 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -94,7 +94,7 @@ constructor( val startingDisappearAnimation: Flow<Runnable> = repository.primaryBouncerStartingDisappearAnimation.filterNotNull() val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it } - val keyguardPosition: Flow<Float> = repository.keyguardPosition + val keyguardPosition: Flow<Float> = repository.keyguardPosition.filterNotNull() val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */ val bouncerExpansion: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 484be9ce1975..1b2a9ebc9ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -49,6 +49,7 @@ import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.ImmersiveModeConfirmation +import com.android.systemui.statusbar.gesture.GesturePointerEventListener import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController import com.android.systemui.statusbar.phone.LockscreenWallpaper @@ -182,6 +183,12 @@ abstract class SystemUICoreStartableModule { @ClassKey(ScreenDecorations::class) abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable + /** Inject into GesturePointerEventHandler. */ + @Binds + @IntoMap + @ClassKey(GesturePointerEventListener::class) + abstract fun bindGesturePointerEventListener(sysui: GesturePointerEventListener): CoreStartable + /** Inject into SessionTracker. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 90e78c608089..dfe67114d9e4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -70,8 +70,7 @@ object Flags { // TODO(b/260335638): Tracking Bug @JvmField - val NOTIFICATION_INLINE_REPLY_ANIMATION = - unreleasedFlag("notification_inline_reply_animation") + val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation") /** Makes sure notification panel is updated before the user switch is complete. */ // TODO(b/278873737): Tracking Bug @@ -285,11 +284,7 @@ object Flags { /** Enables preview loading animation in the wallpaper picker. */ // TODO(b/274443705): Tracking Bug @JvmField - val WALLPAPER_PICKER_PREVIEW_ANIMATION = - unreleasedFlag( - "wallpaper_picker_preview_animation", - teamfood = true - ) + val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation") /** * TODO(b/278086361): Tracking bug diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 93eb103d74e1..8064cc1cce88 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -52,6 +52,7 @@ import com.android.systemui.log.SessionTracker import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import java.io.PrintWriter import java.util.Arrays @@ -71,6 +72,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -200,7 +202,7 @@ constructor( private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) - private val _canRunFaceAuth = MutableStateFlow(true) + private val _canRunFaceAuth = MutableStateFlow(false) override val canRunFaceAuth: StateFlow<Boolean> get() = _canRunFaceAuth @@ -281,7 +283,9 @@ constructor( } else { keyguardRepository.isKeyguardGoingAway }, - userRepository.userSwitchingInProgress, + userRepository.selectedUser.map { + it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS + }, ) .onEach { anyOfThemIsTrue -> if (anyOfThemIsTrue) { @@ -325,6 +329,7 @@ constructor( cancelDetection() } } + .flowOn(mainDispatcher) .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false) .launchIn(applicationScope) } @@ -410,6 +415,7 @@ constructor( cancel() } } + .flowOn(mainDispatcher) .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false) .launchIn(applicationScope) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt index e501ece626b4..635961b0ea01 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt @@ -54,11 +54,7 @@ constructor( if (event.handleAction()) { when (event.keyCode) { KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent() - KeyEvent.KEYCODE_SPACE, - KeyEvent.KEYCODE_ENTER -> - if (isDeviceInteractive()) { - return collapseShadeLockedOrShowPrimaryBouncer() - } + KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent() } } return false @@ -94,22 +90,16 @@ constructor( (statusBarStateController.state != StatusBarState.SHADE) && statusBarKeyguardViewManager.shouldDismissOnMenuPressed() if (shouldUnlockOnMenuPressed) { - return collapseShadeLockedOrShowPrimaryBouncer() + shadeController.animateCollapseShadeForced() + return true } return false } - private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean { - when (statusBarStateController.state) { - StatusBarState.SHADE -> return false - StatusBarState.SHADE_LOCKED -> { - shadeController.animateCollapseShadeForced() - return true - } - StatusBarState.KEYGUARD -> { - statusBarKeyguardViewManager.showPrimaryBouncer(true) - return true - } + private fun dispatchSpaceEvent(): Boolean { + if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) { + shadeController.animateCollapseShadeForced() + return true } return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 2a3f8520a63c..4b8171f60203 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -22,6 +22,8 @@ import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.CoreStartable import com.android.systemui.R +import com.android.systemui.biometrics.data.repository.FacePropertyRepository +import com.android.systemui.biometrics.shared.model.LockoutMode import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton @@ -35,6 +37,7 @@ import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject @@ -51,6 +54,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.coroutines.yield /** * Encapsulates business logic related face authentication being triggered for device entry from @@ -72,6 +76,7 @@ constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val userRepository: UserRepository, + private val facePropertyRepository: FacePropertyRepository, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() @@ -92,7 +97,7 @@ constructor( faceAuthenticationLogger.bouncerVisibilityChanged() runFaceAuth( FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, - fallbackToDetect = true + fallbackToDetect = false ) } .launchIn(applicationScope) @@ -134,16 +139,25 @@ constructor( // User switching should stop face auth and then when it is complete we should trigger face // auth so that the switched user can unlock the device with face auth. - userRepository.userSwitchingInProgress - .pairwise(false) - .onEach { (wasSwitching, isSwitching) -> + userRepository.selectedUser + .pairwise() + .onEach { (previous, curr) -> + val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS + val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS if (!wasSwitching && isSwitching) { repository.pauseFaceAuth() } else if (wasSwitching && !isSwitching) { + val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id) + if (lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED) { + repository.lockoutFaceAuth() + } repository.resumeFaceAuth() + yield() runFaceAuth( FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING, - fallbackToDetect = true + // Fallback to detection if bouncer is not showing so that we can detect a + // face and then show the bouncer to the user if face auth can't run + fallbackToDetect = !primaryBouncerInteractor.isBouncerShowing() ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt new file mode 100644 index 000000000000..c2d2725f4f06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.qualifiers + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyguardRootView diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt index a5b00e09131a..c8dab3223d33 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt @@ -87,7 +87,7 @@ constructor( } addListener( object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { + override fun onAnimationEnd(animation: Animator) { updateIsAnimatingSurface() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt new file mode 100644 index 000000000000..c88737e6bd70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.view + +import android.view.View +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.KeyguardViewConfigurator +import com.android.systemui.keyguard.qualifiers.KeyguardRootView +import dagger.Module +import dagger.Provides +import javax.inject.Provider +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@Module +object LockscreenSceneModule { + + @Provides + @SysUISingleton + @KeyguardRootView + fun viewProvider( + configurator: Provider<KeyguardViewConfigurator>, + ): () -> View { + return { configurator.get().getKeyguardRootView() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 6d3b7f18e974..93c4902332c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -16,41 +16,22 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.R import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the lockscreen scene. */ @SysUISingleton class LockscreenSceneViewModel @Inject constructor( - @Application applicationScope: CoroutineScope, authenticationInteractor: AuthenticationInteractor, private val bouncerInteractor: BouncerInteractor, ) { - /** The icon for the "lock" button on the lockscreen. */ - val lockButtonIcon: StateFlow<Icon> = - authenticationInteractor.isUnlocked - .map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value), - ) - /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: Flow<SceneKey> = authenticationInteractor.isUnlocked.map { isUnlocked -> @@ -65,31 +46,4 @@ constructor( fun onLockButtonClicked() { bouncerInteractor.showOrUnlockDevice() } - - /** Notifies that some content on the lock screen was clicked. */ - fun onContentClicked() { - bouncerInteractor.showOrUnlockDevice() - } - - private fun upDestinationSceneKey( - canSwipeToDismiss: Boolean, - ): SceneKey { - return if (canSwipeToDismiss) SceneKey.Gone else SceneKey.Bouncer - } - - private fun lockIcon( - isUnlocked: Boolean, - ): Icon { - return if (isUnlocked) { - Icon.Resource( - R.drawable.ic_device_lock_off, - ContentDescription.Resource(R.string.accessibility_unlock_button) - ) - } else { - Icon.Resource( - R.drawable.ic_device_lock_on, - ContentDescription.Resource(R.string.accessibility_lock_icon) - ) - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index 72352e397806..a9d2b30c2b6f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -36,6 +36,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Typeface; import android.media.projection.IMediaProjection; +import android.media.projection.MediaProjectionConfig; import android.media.projection.MediaProjectionManager; import android.media.projection.ReviewGrantedConsentResult; import android.os.Bundle; @@ -54,6 +55,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.screenrecord.MediaProjectionPermissionDialog; import com.android.systemui.screenrecord.ScreenShareOption; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -71,6 +73,7 @@ public class MediaProjectionPermissionActivity extends Activity private final FeatureFlags mFeatureFlags; private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; + private final ActivityStarter mActivityStarter; private String mPackageName; private int mUid; @@ -86,8 +89,10 @@ public class MediaProjectionPermissionActivity extends Activity @Inject public MediaProjectionPermissionActivity(FeatureFlags featureFlags, - Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) { + Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, + ActivityStarter activityStarter) { mFeatureFlags = featureFlags; + mActivityStarter = activityStarter; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; } @@ -208,11 +213,13 @@ public class MediaProjectionPermissionActivity extends Activity // the correct screen width when in split screen. Context dialogContext = getApplicationContext(); if (isPartialScreenSharingEnabled()) { - mDialog = new MediaProjectionPermissionDialog(dialogContext, () -> { - ScreenShareOption selectedOption = - ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption(); - grantMediaProjectionPermission(selectedOption.getMode()); - }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName); + mDialog = new MediaProjectionPermissionDialog(dialogContext, getMediaProjectionConfig(), + () -> { + MediaProjectionPermissionDialog dialog = + (MediaProjectionPermissionDialog) mDialog; + ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption(); + grantMediaProjectionPermission(selectedOption.getMode()); + }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName); } else { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog) @@ -306,8 +313,16 @@ public class MediaProjectionPermissionActivity extends Activity // Start activity from the current foreground user to avoid creating a separate // SystemUI process without access to recent tasks because it won't have // WM Shell running inside. + // It is also important to make sure the shade is dismissed, otherwise users won't + // see the app selector. mUserSelectingTask = true; - startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser())); + mActivityStarter.startActivity( + intent, + /* dismissShade= */ true, + /* animationController= */ null, + /* showOverLockscreenWhenLocked= */ false, + UserHandle.of(ActivityManager.getCurrentUser()) + ); } } catch (RemoteException e) { Log.e(TAG, "Error granting projection permission", e); @@ -348,6 +363,16 @@ public class MediaProjectionPermissionActivity extends Activity } } + @Nullable + private MediaProjectionConfig getMediaProjectionConfig() { + Intent intent = getIntent(); + if (intent == null) { + return null; + } + return intent.getParcelableExtra( + MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG); + } + private boolean isPartialScreenSharingEnabled() { return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 9f45f66d9971..1e82d44e413e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -87,7 +87,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBar; @@ -571,7 +570,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis SysUiState sysUiState, Provider<SceneInteractor> sceneInteractor, UserTracker userTracker, - ScreenLifecycle screenLifecycle, WakefulnessLifecycle wakefulnessLifecycle, UiEventLogger uiEventLogger, DisplayTracker displayTracker, @@ -651,7 +649,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Listen for user setup mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); - screenLifecycle.addObserver(mScreenLifecycleObserver); wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver); // Connect to the service updateEnabledAndBinding(); @@ -923,60 +920,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - private final ScreenLifecycle.Observer mScreenLifecycleObserver = - new ScreenLifecycle.Observer() { - /** - * Notifies the Launcher that screen turned on and ready to use - */ - @Override - public void onScreenTurnedOn() { - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurnedOn(); - } else { - Log.e(TAG_OPS, - "Failed to get overview proxy for screen turned on event."); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e); - } - } - - /** - * Notifies the Launcher that screen is starting to turn on. - */ - @Override - public void onScreenTurningOff() { - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurningOff(); - } else { - Log.e(TAG_OPS, - "Failed to get overview proxy for screen turning off event."); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e); - } - } - - /** - * Notifies the Launcher that screen is starting to turn on. - */ - @Override - public void onScreenTurningOn() { - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurningOn(); - } else { - Log.e(TAG_OPS, - "Failed to get overview proxy for screen turning on event."); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e); - } - } - }; - private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver = new WakefulnessLifecycle.Observer() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 398e64b1981b..714795109454 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene +import com.android.systemui.keyguard.ui.view.LockscreenSceneModule import com.android.systemui.scene.domain.startable.SceneContainerStartableModule import com.android.systemui.scene.shared.model.SceneContainerConfigModule import com.android.systemui.scene.ui.composable.SceneModule @@ -24,6 +25,7 @@ import dagger.Module @Module( includes = [ + LockscreenSceneModule::class, SceneContainerConfigModule::class, SceneContainerStartableModule::class, SceneModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt index 23894a3f7835..7859fa0b1d87 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt @@ -18,7 +18,9 @@ package com.android.systemui.screenrecord import android.content.Context import android.os.Bundle import android.view.Gravity +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.view.ViewStub import android.view.WindowManager import android.widget.AdapterView @@ -35,7 +37,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog /** Base permission dialog for screen share and recording */ open class BaseScreenSharePermissionDialog( - context: Context?, + context: Context, private val screenShareOptions: List<ScreenShareOption>, private val appName: String?, @DrawableRes private val dialogIconDrawable: Int? = null, @@ -82,14 +84,7 @@ open class BaseScreenSharePermissionDialog( get() = context.getString(selectedScreenShareOption.warningText, appName) private fun initScreenShareSpinner() { - val options = screenShareOptions.map { context.getString(it.spinnerText) }.toTypedArray() - val adapter = - ArrayAdapter( - context.applicationContext, - R.layout.screen_share_dialog_spinner_text, - options - ) - adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text) + val adapter = OptionsAdapter(context.applicationContext, screenShareOptions) screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner) screenShareModeSpinner.adapter = adapter screenShareModeSpinner.onItemSelectedListener = this @@ -131,3 +126,35 @@ open class BaseScreenSharePermissionDialog( stub.inflate() } } + +private class OptionsAdapter( + context: Context, + private val options: List<ScreenShareOption>, +) : + ArrayAdapter<String>( + context, + R.layout.screen_share_dialog_spinner_text, + options.map { context.getString(it.spinnerText) } + ) { + + override fun isEnabled(position: Int): Boolean { + return options[position].spinnerDisabledText == null + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.screen_share_dialog_spinner_item_text, parent, false) + val titleTextView = view.findViewById<TextView>(android.R.id.text1) + val errorTextView = view.findViewById<TextView>(android.R.id.text2) + titleTextView.text = getItem(position) + errorTextView.text = options[position].spinnerDisabledText + if (isEnabled(position)) { + errorTextView.visibility = View.GONE + titleTextView.isEnabled = true + } else { + errorTextView.visibility = View.VISIBLE + titleTextView.isEnabled = false + } + return view + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt index f4f5f66dfb09..8cbc4aab3bd3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt @@ -16,16 +16,23 @@ package com.android.systemui.screenrecord import android.content.Context +import android.media.projection.MediaProjectionConfig import android.os.Bundle import com.android.systemui.R /** Dialog to select screen recording options */ class MediaProjectionPermissionDialog( - context: Context?, + context: Context, + mediaProjectionConfig: MediaProjectionConfig?, private val onStartRecordingClicked: Runnable, private val onCancelClicked: Runnable, private val appName: String? -) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) { +) : + BaseScreenSharePermissionDialog( + context, + createOptionList(context, appName, mediaProjectionConfig), + appName + ) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // TODO(b/270018943): Handle the case of System sharing (not recording nor casting) @@ -49,7 +56,11 @@ class MediaProjectionPermissionDialog( } companion object { - private fun createOptionList(appName: String?): List<ScreenShareOption> { + private fun createOptionList( + context: Context, + appName: String?, + mediaProjectionConfig: MediaProjectionConfig? + ): List<ScreenShareOption> { val singleAppWarningText = if (appName == null) { R.string.media_projection_entry_cast_permission_dialog_warning_single_app @@ -63,6 +74,19 @@ class MediaProjectionPermissionDialog( R.string.media_projection_entry_app_permission_dialog_warning_entire_screen } + val singleAppDisabledText = + if ( + appName != null && + mediaProjectionConfig?.regionToCapture == + MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY + ) { + context.getString( + R.string.media_projection_entry_app_permission_dialog_single_app_disabled, + appName + ) + } else { + null + } return listOf( ScreenShareOption( mode = ENTIRE_SCREEN, @@ -72,7 +96,8 @@ class MediaProjectionPermissionDialog( ScreenShareOption( mode = SINGLE_APP, spinnerText = R.string.screen_share_permission_dialog_option_single_app, - warningText = singleAppWarningText + warningText = singleAppWarningText, + spinnerDisabledText = singleAppDisabledText, ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index fb99775b6549..9c5da1011bc0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -41,7 +41,7 @@ import com.android.systemui.settings.UserContextProvider /** Dialog to select screen recording options */ class ScreenRecordPermissionDialog( - context: Context?, + context: Context, private val hostUserHandle: UserHandle, private val controller: RecordingController, private val activityStarter: ActivityStarter, @@ -52,7 +52,7 @@ class ScreenRecordPermissionDialog( BaseScreenSharePermissionDialog( context, createOptionList(), - null, + appName = null, R.drawable.ic_screenrecord, R.color.screenrecord_icon_color ) { diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt index 3d39fd82ac63..ebf0dd28fbf4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt @@ -29,5 +29,6 @@ const val SINGLE_APP = 1 class ScreenShareOption( @ScreenShareMode val mode: Int, @StringRes val spinnerText: Int, - @StringRes val warningText: Int + @StringRes val warningText: Int, + val spinnerDisabledText: String? = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index 6c8190af27f7..d0d37c4e685f 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -25,6 +25,7 @@ import android.widget.SeekBar; import androidx.annotation.Nullable; +import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; @@ -52,6 +53,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private BrightnessMirrorController mMirrorController; private boolean mTracking; private final FalsingManager mFalsingManager; + private final UiEventLogger mUiEventLogger; private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() { @Override @@ -72,9 +74,11 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV BrightnessSliderController( BrightnessSliderView brightnessSliderView, - FalsingManager falsingManager) { + FalsingManager falsingManager, + UiEventLogger uiEventLogger) { super(brightnessSliderView); mFalsingManager = falsingManager; + mUiEventLogger = uiEventLogger; } /** @@ -206,7 +210,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV @Override public void onStartTrackingTouch(SeekBar seekBar) { mTracking = true; - + mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH); if (mListener != null) { mListener.onChanged(mTracking, getValue(), false); } @@ -220,7 +224,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV @Override public void onStopTrackingTouch(SeekBar seekBar) { mTracking = false; - + mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH); if (mListener != null) { mListener.onChanged(mTracking, getValue(), true); } @@ -237,10 +241,12 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV public static class Factory { private final FalsingManager mFalsingManager; + private final UiEventLogger mUiEventLogger; @Inject - public Factory(FalsingManager falsingManager) { + public Factory(FalsingManager falsingManager, UiEventLogger uiEventLogger) { mFalsingManager = falsingManager; + mUiEventLogger = uiEventLogger; } /** @@ -250,11 +256,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated * hierarchy will not be attached */ - public BrightnessSliderController create(Context context, @Nullable ViewGroup viewRoot) { + public BrightnessSliderController create( + Context context, + @Nullable ViewGroup viewRoot) { int layout = getLayout(); BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context) .inflate(layout, viewRoot, false); - return new BrightnessSliderController(root, mFalsingManager); + return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger); } /** Get the layout to inflate based on what slider to use */ diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java new file mode 100644 index 000000000000..3a30880eeb5c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settings.brightness; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; + +public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum { + + @UiEvent(doc = "slider started to track touch") + SLIDER_STARTED_TRACKING_TOUCH(1472), + @UiEvent(doc = "slider stopped tracking touch") + SLIDER_STOPPED_TRACKING_TOUCH(1473); + + private final int mId; + + BrightnessSliderEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 59f59aef7e46..ff4570ea0fc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput; - import android.annotation.NonNull; import android.annotation.SuppressLint; import android.app.NotificationChannel; @@ -142,8 +140,6 @@ public class NotificationListener extends NotificationListenerWithPlugins implem if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) { mMainExecutor.execute(() -> { - processForRemoteInput(sbn.getNotification(), mContext); - for (NotificationHandler handler : mNotificationHandlers) { handler.onNotificationPosted(sbn, rankingMap); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index 58d705417632..ac8001058ef6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -17,11 +17,7 @@ package com.android.systemui.statusbar; import android.annotation.Nullable; -import android.app.Notification; -import android.app.RemoteInput; -import android.content.Context; import android.net.Uri; -import android.os.SystemProperties; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.IndentingPrintWriter; @@ -37,16 +33,12 @@ import com.android.systemui.util.DumpUtilsKt; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.List; import java.util.Objects; /** * Keeps track of the currently active {@link RemoteInputView}s. */ public class RemoteInputController { - private static final boolean ENABLE_REMOTE_INPUT = - SystemProperties.getBoolean("debug.enable_remote_input", true); - private final ArrayList<Pair<WeakReference<NotificationEntry>, Object>> mOpen = new ArrayList<>(); private final ArrayMap<String, Object> mSpinning = new ArrayMap<>(); @@ -72,53 +64,6 @@ public class RemoteInputController { } /** - * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this - * via first-class API. - * - * TODO: Remove once enough apps specify remote inputs on their own. - */ - public static void processForRemoteInput(Notification n, Context context) { - if (!ENABLE_REMOTE_INPUT) { - return; - } - - if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") && - (n.actions == null || n.actions.length == 0)) { - Notification.Action viableAction = null; - Notification.WearableExtender we = new Notification.WearableExtender(n); - - List<Notification.Action> actions = we.getActions(); - final int numActions = actions.size(); - - for (int i = 0; i < numActions; i++) { - Notification.Action action = actions.get(i); - if (action == null) { - continue; - } - RemoteInput[] remoteInputs = action.getRemoteInputs(); - if (remoteInputs == null) { - continue; - } - for (RemoteInput ri : remoteInputs) { - if (ri.getAllowFreeFormInput()) { - viableAction = action; - break; - } - } - if (viableAction != null) { - break; - } - } - - if (viableAction != null) { - Notification.Builder rebuilder = Notification.Builder.recoverBuilder(context, n); - rebuilder.setActions(viableAction); - rebuilder.build(); // will rewrite n - } - } - } - - /** * Adds a currently active remote input. * * @param entry the entry for which a remote input is now active. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt new file mode 100644 index 000000000000..b34c3ac5af41 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.gesture + +import android.content.Context +import android.view.InputEvent +import android.view.MotionEvent +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.DisplayTracker +import javax.inject.Inject + +/** + * A class to detect when a motion event happens. To be notified when the event is detected, add a + * callback via [addOnGestureDetectedCallback]. + */ +@SysUISingleton +class GesturePointerEventDetector @Inject constructor( + private val context: Context, + displayTracker: DisplayTracker +) : GenericGestureDetector( + GesturePointerEventDetector::class.simpleName!!, + displayTracker.defaultDisplayId +) { + override fun onInputEvent(ev: InputEvent) { + if (ev !is MotionEvent) { + return + } + // Pass all events to [gestureDetector], which will then notify [gestureListener] when a tap + // is detected. + onGestureDetected(ev) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt new file mode 100644 index 000000000000..8505c5ff32e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.gesture + +import android.content.Context +import android.graphics.Rect +import android.graphics.Region +import android.hardware.display.DisplayManagerGlobal +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.util.Log +import android.view.DisplayCutout +import android.view.DisplayInfo +import android.view.GestureDetector +import android.view.InputDevice +import android.view.InputEvent +import android.view.MotionEvent +import android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT +import android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE +import android.view.ViewRootImpl.CLIENT_TRANSIENT +import android.widget.OverScroller +import com.android.internal.R +import com.android.systemui.CoreStartable +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Watches for gesture events that may trigger system bar related events and notify the registered + * callbacks. Add callback to this listener by calling {@link setCallbacks}. + */ +class GesturePointerEventListener +@Inject +constructor(context: Context, gestureDetector: GesturePointerEventDetector) : CoreStartable { + private val mContext: Context + private val mHandler = Handler(Looper.getMainLooper()) + private var mGestureDetector: GesturePointerEventDetector + private var mFlingGestureDetector: GestureDetector? = null + private var mDisplayCutoutTouchableRegionSize = 0 + + // The thresholds for each edge of the display + private val mSwipeStartThreshold = Rect() + private var mSwipeDistanceThreshold = 0 + private var mCallbacks: Callbacks? = null + private val mDownPointerId = IntArray(MAX_TRACKED_POINTERS) + private val mDownX = FloatArray(MAX_TRACKED_POINTERS) + private val mDownY = FloatArray(MAX_TRACKED_POINTERS) + private val mDownTime = LongArray(MAX_TRACKED_POINTERS) + var screenHeight = 0 + var screenWidth = 0 + private var mDownPointers = 0 + private var mSwipeFireable = false + private var mDebugFireable = false + private var mMouseHoveringAtLeft = false + private var mMouseHoveringAtTop = false + private var mMouseHoveringAtRight = false + private var mMouseHoveringAtBottom = false + private var mLastFlingTime: Long = 0 + + init { + mContext = checkNull("context", context) + mGestureDetector = checkNull("gesture detector", gestureDetector) + onConfigurationChanged() + } + + override fun start() { + if (!CLIENT_TRANSIENT) { + return + } + mGestureDetector.addOnGestureDetectedCallback(TAG) { ev -> onInputEvent(ev) } + mGestureDetector.startGestureListening() + + mFlingGestureDetector = + object : GestureDetector(mContext, FlingGestureDetector(), mHandler) {} + } + + fun onDisplayInfoChanged(info: DisplayInfo) { + screenWidth = info.logicalWidth + screenHeight = info.logicalHeight + onConfigurationChanged() + } + + fun onConfigurationChanged() { + if (!CLIENT_TRANSIENT) { + return + } + val r = mContext.resources + val defaultThreshold = r.getDimensionPixelSize(R.dimen.system_gestures_start_threshold) + mSwipeStartThreshold[defaultThreshold, defaultThreshold, defaultThreshold] = + defaultThreshold + mSwipeDistanceThreshold = defaultThreshold + val display = DisplayManagerGlobal.getInstance().getRealDisplay(mContext.displayId) + val displayCutout = display.cutout + if (displayCutout != null) { + // Expand swipe start threshold such that we can catch touches that just start beyond + // the notch area + mDisplayCutoutTouchableRegionSize = + r.getDimensionPixelSize(R.dimen.display_cutout_touchable_region_size) + val bounds = displayCutout.boundingRectsAll + if (bounds[DisplayCutout.BOUNDS_POSITION_LEFT] != null) { + mSwipeStartThreshold.left = + Math.max( + mSwipeStartThreshold.left, + bounds[DisplayCutout.BOUNDS_POSITION_LEFT]!!.width() + + mDisplayCutoutTouchableRegionSize + ) + } + if (bounds[DisplayCutout.BOUNDS_POSITION_TOP] != null) { + mSwipeStartThreshold.top = + Math.max( + mSwipeStartThreshold.top, + bounds[DisplayCutout.BOUNDS_POSITION_TOP]!!.height() + + mDisplayCutoutTouchableRegionSize + ) + } + if (bounds[DisplayCutout.BOUNDS_POSITION_RIGHT] != null) { + mSwipeStartThreshold.right = + Math.max( + mSwipeStartThreshold.right, + bounds[DisplayCutout.BOUNDS_POSITION_RIGHT]!!.width() + + mDisplayCutoutTouchableRegionSize + ) + } + if (bounds[DisplayCutout.BOUNDS_POSITION_BOTTOM] != null) { + mSwipeStartThreshold.bottom = + Math.max( + mSwipeStartThreshold.bottom, + bounds[DisplayCutout.BOUNDS_POSITION_BOTTOM]!!.height() + + mDisplayCutoutTouchableRegionSize + ) + } + } + if (DEBUG) + Log.d( + TAG, + "mSwipeStartThreshold=$mSwipeStartThreshold" + + " mSwipeDistanceThreshold=$mSwipeDistanceThreshold" + ) + } + + fun onInputEvent(ev: InputEvent) { + if (ev !is MotionEvent) { + return + } + if (DEBUG) Log.d(TAG, "Received motion event $ev") + if (ev.isTouchEvent) { + mFlingGestureDetector?.onTouchEvent(ev) + } + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + mSwipeFireable = true + mDebugFireable = true + mDownPointers = 0 + captureDown(ev, 0) + if (mMouseHoveringAtLeft) { + mMouseHoveringAtLeft = false + mCallbacks?.onMouseLeaveFromLeft() + } + if (mMouseHoveringAtTop) { + mMouseHoveringAtTop = false + mCallbacks?.onMouseLeaveFromTop() + } + if (mMouseHoveringAtRight) { + mMouseHoveringAtRight = false + mCallbacks?.onMouseLeaveFromRight() + } + if (mMouseHoveringAtBottom) { + mMouseHoveringAtBottom = false + mCallbacks?.onMouseLeaveFromBottom() + } + mCallbacks?.onDown() + } + MotionEvent.ACTION_POINTER_DOWN -> { + captureDown(ev, ev.actionIndex) + if (mDebugFireable) { + mDebugFireable = ev.pointerCount < 5 + if (!mDebugFireable) { + if (DEBUG) Log.d(TAG, "Firing debug") + mCallbacks?.onDebug() + } + } + } + MotionEvent.ACTION_MOVE -> + if (mSwipeFireable) { + val trackpadSwipe = detectTrackpadThreeFingerSwipe(ev) + mSwipeFireable = trackpadSwipe == TRACKPAD_SWIPE_NONE + if (!mSwipeFireable) { + if (trackpadSwipe == TRACKPAD_SWIPE_FROM_TOP) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromTop from trackpad") + mCallbacks?.onSwipeFromTop() + } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_BOTTOM) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromBottom from trackpad") + mCallbacks?.onSwipeFromBottom() + } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_RIGHT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromRight from trackpad") + mCallbacks?.onSwipeFromRight() + } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_LEFT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromLeft from trackpad") + mCallbacks?.onSwipeFromLeft() + } + } else { + val swipe = detectSwipe(ev) + mSwipeFireable = swipe == SWIPE_NONE + if (swipe == SWIPE_FROM_TOP) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromTop") + mCallbacks?.onSwipeFromTop() + } else if (swipe == SWIPE_FROM_BOTTOM) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromBottom") + mCallbacks?.onSwipeFromBottom() + } else if (swipe == SWIPE_FROM_RIGHT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromRight") + mCallbacks?.onSwipeFromRight() + } else if (swipe == SWIPE_FROM_LEFT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromLeft") + mCallbacks?.onSwipeFromLeft() + } + } + } + MotionEvent.ACTION_HOVER_MOVE -> + if (ev.isFromSource(InputDevice.SOURCE_MOUSE)) { + val eventX = ev.x + val eventY = ev.y + if (!mMouseHoveringAtLeft && eventX == 0f) { + mCallbacks?.onMouseHoverAtLeft() + mMouseHoveringAtLeft = true + } else if (mMouseHoveringAtLeft && eventX > 0) { + mCallbacks?.onMouseLeaveFromLeft() + mMouseHoveringAtLeft = false + } + if (!mMouseHoveringAtTop && eventY == 0f) { + mCallbacks?.onMouseHoverAtTop() + mMouseHoveringAtTop = true + } else if (mMouseHoveringAtTop && eventY > 0) { + mCallbacks?.onMouseLeaveFromTop() + mMouseHoveringAtTop = false + } + if (!mMouseHoveringAtRight && eventX >= screenWidth - 1) { + mCallbacks?.onMouseHoverAtRight() + mMouseHoveringAtRight = true + } else if (mMouseHoveringAtRight && eventX < screenWidth - 1) { + mCallbacks?.onMouseLeaveFromRight() + mMouseHoveringAtRight = false + } + if (!mMouseHoveringAtBottom && eventY >= screenHeight - 1) { + mCallbacks?.onMouseHoverAtBottom() + mMouseHoveringAtBottom = true + } else if (mMouseHoveringAtBottom && eventY < screenHeight - 1) { + mCallbacks?.onMouseLeaveFromBottom() + mMouseHoveringAtBottom = false + } + } + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> { + mSwipeFireable = false + mDebugFireable = false + mCallbacks?.onUpOrCancel() + } + else -> if (DEBUG) Log.d(TAG, "Ignoring $ev") + } + } + + fun setCallbacks(callbacks: Callbacks) { + mCallbacks = callbacks + } + + private fun captureDown(event: MotionEvent, pointerIndex: Int) { + val pointerId = event.getPointerId(pointerIndex) + val i = findIndex(pointerId) + if (DEBUG) Log.d(TAG, "pointer $pointerId down pointerIndex=$pointerIndex trackingIndex=$i") + if (i != UNTRACKED_POINTER) { + mDownX[i] = event.getX(pointerIndex) + mDownY[i] = event.getY(pointerIndex) + mDownTime[i] = event.eventTime + if (DEBUG) + Log.d(TAG, "pointer " + pointerId + " down x=" + mDownX[i] + " y=" + mDownY[i]) + } + } + + protected fun currentGestureStartedInRegion(r: Region): Boolean { + return r.contains(mDownX[0].toInt(), mDownY[0].toInt()) + } + + private fun findIndex(pointerId: Int): Int { + for (i in 0 until mDownPointers) { + if (mDownPointerId[i] == pointerId) { + return i + } + } + if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) { + return UNTRACKED_POINTER + } + mDownPointerId[mDownPointers++] = pointerId + return mDownPointers - 1 + } + + private fun detectTrackpadThreeFingerSwipe(move: MotionEvent): Int { + if (!isTrackpadThreeFingerSwipe(move)) { + return TRACKPAD_SWIPE_NONE + } + val dx = move.x - mDownX[0] + val dy = move.y - mDownY[0] + if (Math.abs(dx) < Math.abs(dy)) { + if (Math.abs(dy) > mSwipeDistanceThreshold) { + return if (dy > 0) TRACKPAD_SWIPE_FROM_TOP else TRACKPAD_SWIPE_FROM_BOTTOM + } + } else { + if (Math.abs(dx) > mSwipeDistanceThreshold) { + return if (dx > 0) TRACKPAD_SWIPE_FROM_LEFT else TRACKPAD_SWIPE_FROM_RIGHT + } + } + return TRACKPAD_SWIPE_NONE + } + + private fun isTrackpadThreeFingerSwipe(event: MotionEvent): Boolean { + return (event.classification == CLASSIFICATION_MULTI_FINGER_SWIPE && + event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3f) + } + private fun detectSwipe(move: MotionEvent): Int { + val historySize = move.historySize + val pointerCount = move.pointerCount + for (p in 0 until pointerCount) { + val pointerId = move.getPointerId(p) + val i = findIndex(pointerId) + if (i != UNTRACKED_POINTER) { + for (h in 0 until historySize) { + val time = move.getHistoricalEventTime(h) + val x = move.getHistoricalX(p, h) + val y = move.getHistoricalY(p, h) + val swipe = detectSwipe(i, time, x, y) + if (swipe != SWIPE_NONE) { + return swipe + } + } + val swipe = detectSwipe(i, move.eventTime, move.getX(p), move.getY(p)) + if (swipe != SWIPE_NONE) { + return swipe + } + } + } + return SWIPE_NONE + } + + private fun detectSwipe(i: Int, time: Long, x: Float, y: Float): Int { + val fromX = mDownX[i] + val fromY = mDownY[i] + val elapsed = time - mDownTime[i] + if (DEBUG) + Log.d( + TAG, + "pointer " + + mDownPointerId[i] + + " moved (" + + fromX + + "->" + + x + + "," + + fromY + + "->" + + y + + ") in " + + elapsed + ) + if ( + fromY <= mSwipeStartThreshold.top && + y > fromY + mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + return SWIPE_FROM_TOP + } + if ( + fromY >= screenHeight - mSwipeStartThreshold.bottom && + y < fromY - mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + return SWIPE_FROM_BOTTOM + } + if ( + fromX >= screenWidth - mSwipeStartThreshold.right && + x < fromX - mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + return SWIPE_FROM_RIGHT + } + return if ( + fromX <= mSwipeStartThreshold.left && + x > fromX + mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + SWIPE_FROM_LEFT + } else SWIPE_NONE + } + + fun dump(pw: PrintWriter, prefix: String) { + val inner = "$prefix " + pw.println(prefix + TAG + ":") + pw.print(inner) + pw.print("mDisplayCutoutTouchableRegionSize=") + pw.println(mDisplayCutoutTouchableRegionSize) + pw.print(inner) + pw.print("mSwipeStartThreshold=") + pw.println(mSwipeStartThreshold) + pw.print(inner) + pw.print("mSwipeDistanceThreshold=") + pw.println(mSwipeDistanceThreshold) + } + + private inner class FlingGestureDetector internal constructor() : + GestureDetector.SimpleOnGestureListener() { + private val mOverscroller: OverScroller = OverScroller(mContext) + + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (!mOverscroller.isFinished) { + mOverscroller.forceFinished(true) + } + return true + } + + override fun onFling( + down: MotionEvent?, + up: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + mOverscroller.computeScrollOffset() + val now = SystemClock.uptimeMillis() + if (mLastFlingTime != 0L && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) { + mOverscroller.forceFinished(true) + } + mOverscroller.fling( + 0, + 0, + velocityX.toInt(), + velocityY.toInt(), + Int.MIN_VALUE, + Int.MAX_VALUE, + Int.MIN_VALUE, + Int.MAX_VALUE + ) + var duration = mOverscroller.duration + if (duration > MAX_FLING_TIME_MILLIS) { + duration = MAX_FLING_TIME_MILLIS + } + mLastFlingTime = now + mCallbacks?.onFling(duration) + return true + } + } + + interface Callbacks { + fun onSwipeFromTop() + fun onSwipeFromBottom() + fun onSwipeFromRight() + fun onSwipeFromLeft() + fun onFling(durationMs: Int) + fun onDown() + fun onUpOrCancel() + fun onMouseHoverAtLeft() + fun onMouseHoverAtTop() + fun onMouseHoverAtRight() + fun onMouseHoverAtBottom() + fun onMouseLeaveFromLeft() + fun onMouseLeaveFromTop() + fun onMouseLeaveFromRight() + fun onMouseLeaveFromBottom() + fun onDebug() + } + + companion object { + private const val TAG = "GesturePointerEventHandler" + private const val DEBUG = false + private const val SWIPE_TIMEOUT_MS: Long = 500 + private const val MAX_TRACKED_POINTERS = 32 // max per input system + private const val UNTRACKED_POINTER = -1 + private const val MAX_FLING_TIME_MILLIS = 5000 + private const val SWIPE_NONE = 0 + private const val SWIPE_FROM_TOP = 1 + private const val SWIPE_FROM_BOTTOM = 2 + private const val SWIPE_FROM_RIGHT = 3 + private const val SWIPE_FROM_LEFT = 4 + private const val TRACKPAD_SWIPE_NONE = 0 + private const val TRACKPAD_SWIPE_FROM_TOP = 1 + private const val TRACKPAD_SWIPE_FROM_BOTTOM = 2 + private const val TRACKPAD_SWIPE_FROM_RIGHT = 3 + private const val TRACKPAD_SWIPE_FROM_LEFT = 4 + + private fun <T> checkNull(name: String, arg: T?): T { + requireNotNull(arg) { "$name must not be null" } + return arg + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 62a0d138fd05..5c2f9a8d28ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -39,7 +39,10 @@ class StackCoordinator @Inject internal constructor( override fun attach(pipeline: NotifPipeline) { pipeline.addOnAfterRenderListListener(::onAfterRenderList) - groupExpansionManagerImpl.attach(pipeline) + // TODO(b/282865576): This has an issue where it makes changes to some groups without + // notifying listeners. To be fixed in QPR, but for now let's comment it out to avoid the + // group expansion bug. + // groupExpansionManagerImpl.attach(pipeline) } fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index 5d33804ab6a7..46af03a438f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -67,29 +67,18 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl * Cleanup entries from mExpandedGroups that no longer exist in the pipeline. */ private final OnBeforeRenderListListener mNotifTracker = (entries) -> { - if (mExpandedGroups.isEmpty()) { - return; // nothing to do - } - final Set<NotificationEntry> renderingSummaries = new HashSet<>(); for (ListEntry entry : entries) { if (entry instanceof GroupEntry) { renderingSummaries.add(entry.getRepresentativeEntry()); } } - - // Create a copy of mExpandedGroups so we can modify it in a thread-safe way. - final var currentExpandedGroups = new HashSet<>(mExpandedGroups); - for (NotificationEntry entry : currentExpandedGroups) { - setExpanded(entry, renderingSummaries.contains(entry)); - } + mExpandedGroups.removeIf(expandedGroup -> !renderingSummaries.contains(expandedGroup)); }; public void attach(NotifPipeline pipeline) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - mDumpManager.registerDumpable(this); - pipeline.addOnBeforeRenderListListener(mNotifTracker); - } + mDumpManager.registerDumpable(this); + pipeline.addOnBeforeRenderListListener(mNotifTracker); } @Override @@ -105,24 +94,11 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl @Override public void setGroupExpanded(NotificationEntry entry, boolean expanded) { final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry); - setExpanded(groupSummary, expanded); - } - - /** - * Add or remove {@code entry} to/from {@code mExpandedGroups} and notify listeners if - * something changed. This assumes that {@code entry} is a group summary. - * <p> - * TODO(b/293434635): Currently, in spite of its docs, - * {@code mGroupMembershipManager.getGroupSummary(entry)} returns null if {@code entry} is - * already a summary. Instead of needing this helper method to bypass that, we probably want to - * move this code back to {@code setGroupExpanded} and use that everywhere. - */ - private void setExpanded(NotificationEntry entry, boolean expanded) { boolean changed; if (expanded) { - changed = mExpandedGroups.add(entry); + changed = mExpandedGroups.add(groupSummary); } else { - changed = mExpandedGroups.remove(entry); + changed = mExpandedGroups.remove(groupSummary); } // Only notify listeners if something changed. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt index 4429939a515c..d10b556de0fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt @@ -21,29 +21,25 @@ import android.util.AttributeSet import android.util.Log import android.view.LayoutInflater import android.view.View -import com.android.systemui.Dumpable -import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES -import com.android.systemui.util.asIndenting -import com.android.systemui.util.withIncreasedIndent -import java.io.PrintWriter -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import javax.inject.Named /** * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of * [NotifRemoteViewsFactory] objects to create replacement views for Notification RemoteViews. */ -open class NotifLayoutInflaterFactory -@Inject +class NotifLayoutInflaterFactory +@AssistedInject constructor( - dumpManager: DumpManager, + @Assisted private val row: ExpandableNotificationRow, + @Assisted @InflationFlag val layoutType: Int, @Named(NOTIF_REMOTEVIEWS_FACTORIES) private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory> -) : LayoutInflater.Factory2, Dumpable { - init { - dumpManager.registerNormalDumpable(TAG, this) - } +) : LayoutInflater.Factory2 { override fun onCreateView( parent: View?, @@ -51,41 +47,32 @@ constructor( context: Context, attrs: AttributeSet ): View? { - var view: View? = null var handledFactory: NotifRemoteViewsFactory? = null + var result: View? = null for (layoutFactory in remoteViewsFactories) { - view = layoutFactory.instantiate(parent, name, context, attrs) - if (view != null) { + layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run { check(handledFactory == null) { - "${layoutFactory.javaClass.name} tries to produce view. However, " + - "${handledFactory?.javaClass?.name} produced view for $name before." + "$layoutFactory tries to produce name:$name with type:$layoutType. " + + "However, $handledFactory produced view for $name before." } handledFactory = layoutFactory + result = this } } - logOnCreateView(name, view, handledFactory) - return view + logOnCreateView(name, result, handledFactory) + return result } override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? = onCreateView(null, name, context, attrs) - override fun dump(pw: PrintWriter, args: Array<out String>) { - val indentingPW = pw.asIndenting() - - indentingPW.appendLine("$TAG ReplacementFactories:") - indentingPW.withIncreasedIndent { - remoteViewsFactories.forEach { indentingPW.appendLine(it.javaClass.simpleName) } - } - } - private fun logOnCreateView( name: String, replacedView: View?, factory: NotifRemoteViewsFactory? ) { if (SPEW && replacedView != null && factory != null) { - Log.d(TAG, "$factory produced view for $name: $replacedView") + Log.d(TAG, "$factory produced $replacedView for name:$name with type:$layoutType") } } @@ -93,4 +80,12 @@ constructor( private const val TAG = "NotifLayoutInflaterFac" private val SPEW = Log.isLoggable(TAG, Log.VERBOSE) } + + @AssistedFactory + interface Provider { + fun provide( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int + ): NotifLayoutInflaterFactory + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt index eebd4d4e955f..bf740e94f81c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt @@ -19,10 +19,18 @@ package com.android.systemui.statusbar.notification.row import android.content.Context import android.util.AttributeSet import android.view.View +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag /** Interface used to create replacement view instances in Notification RemoteViews. */ interface NotifRemoteViewsFactory { /** return the replacement view instance for the given view name */ - fun instantiate(parent: View?, name: String, context: Context, attrs: AttributeSet): View? + fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet + ): View? } 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 0ad77bdd866b..86f545dc190e 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 @@ -79,7 +79,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final ConversationNotificationProcessor mConversationProcessor; private final Executor mBgExecutor; private final SmartReplyStateInflater mSmartReplyStateInflater; - private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; + private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; @Inject NotificationContentInflater( @@ -89,14 +89,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder MediaFeatureFlag mediaFeatureFlag, @Background Executor bgExecutor, SmartReplyStateInflater smartRepliesInflater, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { mRemoteViewCache = remoteViewCache; mRemoteInputManager = remoteInputManager; mConversationProcessor = conversationProcessor; mIsMediaInQS = mediaFeatureFlag.getEnabled(); mBgExecutor = bgExecutor; mSmartReplyStateInflater = smartRepliesInflater; - mNotifLayoutInflaterFactory = notifLayoutInflaterFactory; + mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider; } @Override @@ -141,7 +141,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteInputManager.getRemoteViewsOnClickHandler(), mIsMediaInQS, mSmartReplyStateInflater, - mNotifLayoutInflaterFactory); + mNotifLayoutInflaterFactoryProvider); if (mInflateSynchronously) { task.onPostExecute(task.doInBackground()); } else { @@ -165,7 +165,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, packageContext, - mNotifLayoutInflaterFactory); + row, + mNotifLayoutInflaterFactoryProvider); result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, @@ -304,7 +305,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags, Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, Context packageContext, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { + ExpandableNotificationRow row, + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { InflationProgress result = new InflationProgress(); if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { @@ -322,7 +324,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { result.newPublicView = builder.makePublicContentView(isLowPriority); } - setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory); + setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider); result.packageContext = packageContext; result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */); result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText( @@ -331,12 +333,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder } private static void setNotifsViewsInflaterFactory(InflationProgress result, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { - setRemoteViewsInflaterFactory(result.newContentView, notifLayoutInflaterFactory); + ExpandableNotificationRow row, + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { + setRemoteViewsInflaterFactory(result.newContentView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED)); setRemoteViewsInflaterFactory(result.newExpandedView, - notifLayoutInflaterFactory); - setRemoteViewsInflaterFactory(result.newHeadsUpView, notifLayoutInflaterFactory); - setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactory); + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_EXPANDED)); + setRemoteViewsInflaterFactory(result.newHeadsUpView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP)); + setRemoteViewsInflaterFactory(result.newPublicView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)); } private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews, @@ -821,7 +827,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final ConversationNotificationProcessor mConversationProcessor; private final boolean mIsMediaInQS; private final SmartReplyStateInflater mSmartRepliesInflater; - private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; + private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; private AsyncInflationTask( Executor bgExecutor, @@ -838,7 +844,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder RemoteViews.InteractionHandler remoteViewClickHandler, boolean isMediaFlagEnabled, SmartReplyStateInflater smartRepliesInflater, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { mEntry = entry; mRow = row; mBgExecutor = bgExecutor; @@ -854,7 +860,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mCallback = callback; mConversationProcessor = conversationProcessor; mIsMediaInQS = isMediaFlagEnabled; - mNotifLayoutInflaterFactory = notifLayoutInflaterFactory; + mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider; entry.setInflationTask(this); } @@ -898,8 +904,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight, - mUsesIncreasedHeadsUpHeight, packageContext, - mNotifLayoutInflaterFactory); + mUsesIncreasedHeadsUpHeight, packageContext, mRow, + mNotifLayoutInflaterFactoryProvider); InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState(); InflationProgress result = inflateSmartReplyViews( inflationProgress, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java index 51e4537d7348..4ace19480984 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java @@ -21,6 +21,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Trace; import androidx.annotation.MainThread; import androidx.annotation.NonNull; @@ -69,6 +70,7 @@ public class NotificationSettingsController implements Dumpable { mContentObserver = new ContentObserver(mBackgroundHandler) { @Override public void onChange(boolean selfChange, Uri uri) { + Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".ContentObserver.onChange"); super.onChange(selfChange, uri); synchronized (mListeners) { if (mListeners.containsKey(uri)) { @@ -79,12 +81,15 @@ public class NotificationSettingsController implements Dumpable { } } } + Trace.traceEnd(Trace.TRACE_TAG_APP); } }; mCurrentUserTrackerCallback = new UserTracker.Callback() { @Override public void onUserChanged(int newUser, Context userContext) { + Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".UserTracker.Callback.onUserChanged"); + synchronized (mListeners) { if (mListeners.size() > 0) { mSecureSettings.unregisterContentObserver(mContentObserver); @@ -94,6 +99,7 @@ public class NotificationSettingsController implements Dumpable { } } } + Trace.traceEnd(Trace.TRACE_TAG_APP); } }; mUserTracker.addCallback( @@ -113,6 +119,7 @@ public class NotificationSettingsController implements Dumpable { if (uri == null || listener == null) { return; } + Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".addCallback"); synchronized (mListeners) { ArrayList<Listener> currentListeners = mListeners.get(uri); if (currentListeners == null) { @@ -132,10 +139,12 @@ public class NotificationSettingsController implements Dumpable { String value = getCurrentSettingValue(uri, userId); mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value)); }); - + Trace.traceEnd(Trace.TRACE_TAG_APP); } public void removeCallback(Uri uri, Listener listener) { + Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".removeCallback"); + synchronized (mListeners) { ArrayList<Listener> currentListeners = mListeners.get(uri); @@ -150,10 +159,13 @@ public class NotificationSettingsController implements Dumpable { mSecureSettings.unregisterContentObserver(mContentObserver); } } + Trace.traceEnd(Trace.TRACE_TAG_APP); } @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".dump"); + synchronized (mListeners) { pw.println("Settings Uri Listener List:"); for (Uri uri : mListeners.keySet()) { @@ -163,6 +175,7 @@ public class NotificationSettingsController implements Dumpable { } } } + Trace.traceEnd(Trace.TRACE_TAG_APP); } private String getCurrentSettingValue(Uri uri, int userId) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt index c27682726da5..96547db1283a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt @@ -23,10 +23,13 @@ import android.widget.TextView import com.android.internal.widget.ConversationLayout import com.android.internal.widget.ImageFloatingTextView import com.android.internal.widget.MessagingLayout +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import javax.inject.Inject class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory { override fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, parent: View?, name: String, context: Context, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index ea57eb46bab4..27b8406e3bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -92,6 +92,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -101,7 +103,6 @@ import java.util.Set; import javax.inject.Inject; -import dagger.Lazy; import kotlinx.coroutines.CoroutineDispatcher; /** @@ -450,14 +451,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } - private KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide()); - } - }; - private void registerListeners() { mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback); mStatusBarStateController.addCallback(this); @@ -471,7 +464,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mDockManager.addListener(mDockEventListener); mIsDocked = mDockManager.isDocked(); } - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { mShadeViewController.postToView(() -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index cd1afc727346..37f032b464b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -226,7 +226,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( val builder = InteractionJankMonitor.Configuration.Builder .withView( InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD, - notifShadeWindowControllerLazy.get().windowRootView + checkNotNull(notifShadeWindowControllerLazy.get().windowRootView) ) .setTag(statusBarStateControllerImpl.getClockId()) diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt index 2325acfdcd48..f5decaaf451e 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt @@ -17,11 +17,13 @@ package com.android.systemui.unfold import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.updates.FoldProvider import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener import java.util.concurrent.Executor -class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider) : +class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider, + private val foldProvider: FoldProvider) : ShellUnfoldProgressProvider { override fun addListener(executor: Executor, listener: UnfoldListener) { @@ -39,5 +41,11 @@ class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitio executor.execute { listener.onStateChangeFinished() } } }) + + foldProvider.registerCallback(object : FoldProvider.FoldCallback { + override fun onFoldUpdated(isFolded: Boolean) { + listener.onFoldStateChanged(isFolded) + } + }, executor) } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 992b0221068c..ed3eacd27c0a 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -118,6 +118,7 @@ class UnfoldTransitionModule { @Singleton fun provideShellProgressProvider( config: UnfoldTransitionConfig, + foldProvider: FoldProvider, provider: Provider<Optional<UnfoldTransitionProgressProvider>>, @Named(UNFOLD_ONLY_PROVIDER) unfoldOnlyProvider: Provider<Optional<UnfoldTransitionProgressProvider>> @@ -135,8 +136,9 @@ class UnfoldTransitionModule { null } - return resultingProvider?.get()?.orElse(null)?.let(::UnfoldProgressProvider) - ?: ShellUnfoldProgressProvider.NO_PROVIDER + return resultingProvider?.get()?.orElse(null)?.let { + unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider) + } ?: ShellUnfoldProgressProvider.NO_PROVIDER } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 954765c4581d..8cfa55570723 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -31,7 +31,6 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus @@ -49,7 +48,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart @@ -77,9 +75,6 @@ interface UserRepository { /** [UserInfo] of the currently-selected user. */ val selectedUserInfo: Flow<UserInfo> - /** Whether user switching is currently in progress. */ - val userSwitchingInProgress: Flow<Boolean> - /** User ID of the main user. */ val mainUserId: Int @@ -162,10 +157,6 @@ constructor( private var _isGuestUserResetting: Boolean = false override var isGuestUserResetting: Boolean = _isGuestUserResetting - private val _isUserSwitchingInProgress = MutableStateFlow(false) - override val userSwitchingInProgress: Flow<Boolean> - get() = _isUserSwitchingInProgress - override val isGuestUserCreationScheduled = AtomicBoolean() override val isStatusBarUserChipEnabled: Boolean = @@ -175,12 +166,6 @@ constructor( override var isRefreshUsersPaused: Boolean = false - init { - if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) { - observeUserSwitching() - } - } - override val selectedUser: StateFlow<SelectedUserModel> = run { // Some callbacks don't modify the selection status, so maintain the current value. var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE @@ -259,28 +244,6 @@ constructor( return _userSwitcherSettings.value.isUserSwitcherEnabled } - private fun observeUserSwitching() { - conflatedCallbackFlow { - val callback = - object : UserTracker.Callback { - override fun onUserChanging(newUser: Int, userContext: Context) { - trySendWithFailureLogging(true, TAG, "userSwitching started") - } - - override fun onUserChanged(newUserId: Int, userContext: Context) { - trySendWithFailureLogging(false, TAG, "userSwitching completed") - } - } - tracker.addCallback(callback, mainDispatcher.asExecutor()) - trySendWithFailureLogging(false, TAG, "initial value defaulting to false") - awaitClose { tracker.removeCallback(callback) } - } - .onEach { _isUserSwitchingInProgress.value = it } - // TODO (b/262838215), Make this stateIn and initialize directly in field declaration - // once the flag is launched - .launchIn(applicationScope) - } - private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt index fcc40404bf7d..0da7b4ac88f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt @@ -17,14 +17,19 @@ package com.android.systemui.biometrics.data.repository +import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE +import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT +import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED import android.hardware.biometrics.SensorProperties import android.hardware.face.FaceManager import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.face.IFaceAuthenticatorsRegisteredCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.shared.model.LockoutMode import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -74,18 +79,52 @@ class FacePropertyRepositoryImplTest : SysuiTestCase() { @Test fun providesTheValuePassedToTheAuthenticatorsRegisteredCallback() { testScope.runTest { - val sensor by collectLastValue(underTest.sensorInfo) runCurrent() verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture()) callback.value.onAllAuthenticatorsRegistered( listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG)) ) + runCurrent() + val sensor by collectLastValue(underTest.sensorInfo) assertThat(sensor).isEqualTo(FaceSensorInfo(1, SensorStrength.STRONG)) } } + @Test + fun providesTheNoneLockoutModeWhenFaceManagerIsNotAvailable() = + testScope.runTest { + underTest = createRepository(null) + + assertThat(underTest.getLockoutMode(-1)).isEqualTo(LockoutMode.NONE) + } + + @Test + fun providesTheLockoutModeFromFaceManager() = + testScope.runTest { + val sensorId = 99 + val userId = 999 + runCurrent() + verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture()) + callback.value.onAllAuthenticatorsRegistered( + listOf(createSensorProperties(sensorId, SensorProperties.STRENGTH_STRONG)) + ) + runCurrent() + + whenever(faceManager.getLockoutModeForUser(sensorId, userId)) + .thenReturn(BIOMETRIC_LOCKOUT_TIMED) + assertThat(underTest.getLockoutMode(userId)).isEqualTo(LockoutMode.TIMED) + + whenever(faceManager.getLockoutModeForUser(sensorId, userId)) + .thenReturn(BIOMETRIC_LOCKOUT_PERMANENT) + assertThat(underTest.getLockoutMode(userId)).isEqualTo(LockoutMode.PERMANENT) + + whenever(faceManager.getLockoutModeForUser(sensorId, userId)) + .thenReturn(BIOMETRIC_LOCKOUT_NONE) + assertThat(underTest.getLockoutMode(userId)).isEqualTo(LockoutMode.NONE) + } + private fun createSensorProperties(id: Int, strength: Int) = FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt index da55d5a099b7..95b72d554896 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt @@ -28,7 +28,7 @@ import org.junit.runners.Parameterized.Parameters @SmallTest @RunWith(Parameterized::class) class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() { - val underTest = BoundingBoxOverlapDetector() + val underTest = BoundingBoxOverlapDetector(1f) @Test fun isGoodOverlap() { @@ -83,7 +83,7 @@ private val TOUCH_DATA = GESTURE_START ) -private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */) +private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */) private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */) private fun genTestCases( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 11b0b0798ebc..47084c087952 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -312,10 +312,13 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(message).isEqualTo(PromptMessage.Empty) assertThat(messageVisible).isFalse() } + val clearIconError = !restart assertThat(legacyState) .isEqualTo( if (restart) { AuthBiometricView.STATE_AUTHENTICATING + } else if (clearIconError) { + AuthBiometricView.STATE_IDLE } else { AuthBiometricView.STATE_HELP } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index 8236165b62d8..d4bba723a989 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -29,6 +29,8 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry @@ -156,4 +158,24 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { assertThat(isShowing).isEqualTo(false) job.cancel() } + + @Test + fun keyguardPosition_noValueSet_emptyByDefault() = runTest { + val positionValues by collectValues(underTest.keyguardPosition) + + runCurrent() + + assertThat(positionValues).isEmpty() + } + + @Test + fun keyguardPosition_valueSet_returnsValue() = runTest { + val position by collectLastValue(underTest.keyguardPosition) + runCurrent() + + repository.setKeyguardPosition(123f) + runCurrent() + + assertThat(position).isEqualTo(123f) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 64b94707da57..f0dbaf1aeaf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -71,6 +71,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.FakeKeyguardStateController import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.KotlinArgumentCaptor import com.android.systemui.util.mockito.captureMany @@ -295,6 +296,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { fun faceAuthDoesNotRunWhileItIsAlreadyRunning() = testScope.runTest { initCollectors() + allPreconditionsToRunFaceAuthAreTrue() underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() @@ -311,6 +313,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { testScope.runTest { initCollectors() verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture()) + allPreconditionsToRunFaceAuthAreTrue() underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() @@ -364,6 +367,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { fun cancelStopsFaceAuthentication() = testScope.runTest { initCollectors() + allPreconditionsToRunFaceAuthAreTrue() underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() @@ -417,6 +421,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { fun faceAuthShouldWaitAndRunIfTriggeredWhileCancelling() = testScope.runTest { initCollectors() + allPreconditionsToRunFaceAuthAreTrue() underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() @@ -492,6 +497,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() + allPreconditionsToRunFaceAuthAreTrue() underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() @@ -803,7 +809,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authenticated()).isTrue() - fakeUserRepository.setUserSwitching(true) + fakeUserRepository.setSelectedUserInfo( + primaryUser, + SelectionStatus.SELECTION_IN_PROGRESS + ) assertThat(authenticated()).isFalse() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 93f208ee14f5..ec115738df8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor +import android.content.pm.UserInfo import android.hardware.biometrics.BiometricFaceConstants import android.os.Handler import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -25,6 +26,8 @@ import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository +import com.android.systemui.biometrics.shared.model.LockoutMode import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor @@ -48,6 +51,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -76,6 +80,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository private lateinit var fakeUserRepository: FakeUserRepository + private lateinit var facePropertyRepository: FakeFacePropertyRepository private lateinit var fakeDeviceEntryFingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository @@ -101,6 +106,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() fakeUserRepository = FakeUserRepository() + fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser)) + facePropertyRepository = FakeFacePropertyRepository() underTest = SystemUIKeyguardFaceAuthInteractor( mContext, @@ -136,6 +143,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, fakeDeviceEntryFingerprintAuthRepository, fakeUserRepository, + facePropertyRepository, ) } @@ -220,9 +228,12 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { testScope.runTest { underTest.start() - fakeUserRepository.setUserSwitching(false) + fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE) runCurrent() - fakeUserRepository.setUserSwitching(true) + fakeUserRepository.setSelectedUserInfo( + secondaryUser, + SelectionStatus.SELECTION_IN_PROGRESS + ) runCurrent() assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue() @@ -234,17 +245,27 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { underTest.start() // previously running - fakeUserRepository.setUserSwitching(true) + fakeUserRepository.setSelectedUserInfo( + primaryUser, + SelectionStatus.SELECTION_IN_PROGRESS + ) runCurrent() - fakeUserRepository.setUserSwitching(false) + bouncerRepository.setPrimaryShow(true) + + facePropertyRepository.setLockoutMode(secondaryUser.id, LockoutMode.TIMED) + fakeUserRepository.setSelectedUserInfo( + secondaryUser, + SelectionStatus.SELECTION_COMPLETE + ) runCurrent() assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse() + assertThat(faceAuthRepository.wasDisabled).isTrue() runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value!!.first) .isEqualTo(FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING) - assertThat(faceAuthRepository.runningAuthRequest.value!!.second).isEqualTo(true) + assertThat(faceAuthRepository.runningAuthRequest.value!!.second).isEqualTo(false) } @Test @@ -259,7 +280,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value) - .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, true)) + .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, false)) } @Test @@ -387,4 +408,11 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { assertThat(faceAuthRepository.wasDisabled).isTrue() } + + companion object { + private const val primaryUserId = 1 + private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY) + + private val secondaryUser = UserInfo(2, "secondary user", 0) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt index e0ae0c359f07..a3f7fc5fc8cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt @@ -131,12 +131,14 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { } @Test - fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_showsPrimaryBouncer() { + fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() { keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test @@ -145,48 +147,42 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_doNothing() { + fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() { keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionsDoNothing(KeyEvent.KEYCODE_MENU) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_showsPrimaryBouncer() { - keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() { - keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) - - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_enterActionUp_interactiveKeyguard_showsPrimaryBouncer() { + fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() { keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER) - } - - @Test - fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() { - keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER) + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test @@ -256,42 +252,4 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { .isFalse() verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any()) } - - private fun verifyActionUpCollapsesTheShade(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() - } - - private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true)) - } - - private fun verifyActionsDoNothing(keycode: Int) { - // action down: does nothing - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: doesNothing - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 23f243c8ebda..a9f288d3575f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -17,10 +17,8 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest -import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel -import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -48,7 +46,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val underTest = LockscreenSceneViewModel( - applicationScope = testScope.backgroundScope, authenticationInteractor = authenticationInteractor, bouncerInteractor = utils.bouncerInteractor( @@ -58,32 +55,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { ) @Test - fun lockButtonIcon_whenLocked() = - testScope.runTest { - val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - utils.authenticationRepository.setUnlocked(false) - - assertThat((lockButtonIcon as? Icon.Resource)?.res) - .isEqualTo(R.drawable.ic_device_lock_on) - } - - @Test - fun lockButtonIcon_whenUnlocked() = - testScope.runTest { - val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - utils.authenticationRepository.setUnlocked(true) - - assertThat((lockButtonIcon as? Icon.Resource)?.res) - .isEqualTo(R.drawable.ic_device_lock_off) - } - - @Test fun upTransitionSceneKey_canSwipeToUnlock_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) @@ -120,32 +91,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { } @Test - fun onContentClicked_deviceUnlocked_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - } - - @Test fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 49ece66e0cfd..ef07fab70bb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -31,7 +31,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.model.SysUiState import com.android.systemui.navigationbar.NavigationBarController @@ -84,7 +83,6 @@ class OverviewProxyServiceTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() private val sysUiState = SysUiState(displayTracker) private val featureFlags = FakeFeatureFlags() - private val screenLifecycle = ScreenLifecycle(dumpManager) private val wakefulnessLifecycle = WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager) @@ -142,7 +140,6 @@ class OverviewProxyServiceTest : SysuiTestCase() { sysUiState, mock(), userTracker, - screenLifecycle, wakefulnessLifecycle, uiEventLogger, displayTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 53c04ccbdb38..46cbfacb0044 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -116,7 +116,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val lockscreenSceneViewModel = LockscreenSceneViewModel( - applicationScope = testScope.backgroundScope, authenticationInteractor = authenticationInteractor, bouncerInteractor = bouncerInteractor, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt index 2b39354d99e8..d75405fe503f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt @@ -20,6 +20,7 @@ import android.testing.AndroidTestingRunner import android.view.MotionEvent import android.widget.SeekBar import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake import com.android.settingslib.RestrictedLockUtils import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -39,6 +40,7 @@ import org.mockito.Mock import org.mockito.Mockito.isNull import org.mockito.Mockito.never import org.mockito.Mockito.notNull +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @@ -64,6 +66,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener> @Mock private lateinit var seekBar: SeekBar + private val uiEventLogger = UiEventLoggerFake() private var mFalsingManager: FalsingManagerFake = FalsingManagerFake() private lateinit var mController: BrightnessSliderController @@ -75,7 +78,8 @@ class BrightnessSliderControllerTest : SysuiTestCase() { whenever(mirrorController.toggleSlider).thenReturn(mirror) whenever(motionEvent.copy()).thenReturn(motionEvent) - mController = BrightnessSliderController(brightnessSliderView, mFalsingManager) + mController = + BrightnessSliderController(brightnessSliderView, mFalsingManager, uiEventLogger) mController.init() mController.setOnChangedListener(listener) } @@ -190,6 +194,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { @Test fun testSeekBarTrackingStarted() { whenever(brightnessSliderView.value).thenReturn(42) + val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH mController.onViewAttached() mController.setMirrorControllerAndMirror(mirrorController) @@ -200,11 +205,14 @@ class BrightnessSliderControllerTest : SysuiTestCase() { verify(listener).onChanged(eq(true), eq(42), eq(false)) verify(mirrorController).showMirror() verify(mirrorController).setLocationAndSize(brightnessSliderView) + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id) } @Test fun testSeekBarTrackingStopped() { whenever(brightnessSliderView.value).thenReturn(23) + val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH mController.onViewAttached() mController.setMirrorControllerAndMirror(mirrorController) @@ -214,5 +222,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { verify(listener).onChanged(eq(false), eq(23), eq(true)) verify(mirrorController).hideMirror() + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index ecaf13711b52..48665fe0c9b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -340,9 +340,9 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun knownPluginAttached_clockAndListChanged_notLoaded() { val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.falcon.one") + whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.clocks.metro") val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.falcon.two") + whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.clocks.bignum") var changeCallCount = 0 var listChangeCallCount = 0 diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt index 9393a4f4eead..ee3d87089b6d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt @@ -60,18 +60,4 @@ class RotationButtonControllerTest : SysuiTestCase() { assertThat(mController.canShowRotationButton()).isTrue() } - - @Test - fun ifTaskbarVisible_showRotationSuggestion() { - mController.onNavigationBarWindowVisibilityChange( /* showing = */ false) - mController.onBehaviorChanged(Display.DEFAULT_DISPLAY, - WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) - mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON) - mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false) - assertThat(mController.canShowRotationButton()).isFalse() - - mController.onTaskbarStateChange( /* visible = */ true, /* stashed = */ false) - - assertThat(mController.canShowRotationButton()).isTrue() - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index 38a8f414b0fb..4a94dc819a9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt @@ -21,21 +21,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder -import com.android.systemui.statusbar.notification.collection.ListEntry -import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener -import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor import org.junit.Assert import org.junit.Before import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest @@ -46,43 +36,13 @@ class GroupExpansionManagerTest : SysuiTestCase() { private val groupMembershipManager: GroupMembershipManager = mock() private val featureFlags = FakeFeatureFlags() - private val pipeline: NotifPipeline = mock() - private lateinit var beforeRenderListListener: OnBeforeRenderListListener - - private val summary1 = notificationEntry("foo", 1) - private val summary2 = notificationEntry("bar", 1) - private val entries = - listOf<ListEntry>( - GroupEntryBuilder() - .setSummary(summary1) - .setChildren( - listOf( - notificationEntry("foo", 2), - notificationEntry("foo", 3), - notificationEntry("foo", 4) - ) - ) - .build(), - GroupEntryBuilder() - .setSummary(summary2) - .setChildren( - listOf( - notificationEntry("bar", 2), - notificationEntry("bar", 3), - notificationEntry("bar", 4) - ) - ) - .build(), - notificationEntry("baz", 1) - ) - - private fun notificationEntry(pkg: String, id: Int) = - NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() } + private val entry1 = NotificationEntryBuilder().build() + private val entry2 = NotificationEntryBuilder().build() @Before fun setUp() { - whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1) - whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2) + whenever(groupMembershipManager.getGroupSummary(entry1)).thenReturn(entry1) + whenever(groupMembershipManager.getGroupSummary(entry2)).thenReturn(entry2) gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags) } @@ -94,15 +54,15 @@ class GroupExpansionManagerTest : SysuiTestCase() { var listenerCalledCount = 0 gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - gem.setGroupExpanded(summary1, false) + gem.setGroupExpanded(entry1, false) Assert.assertEquals(0, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(1, listenerCalledCount) - gem.setGroupExpanded(summary2, true) + gem.setGroupExpanded(entry2, true) Assert.assertEquals(2, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(2, listenerCalledCount) - gem.setGroupExpanded(summary2, false) + gem.setGroupExpanded(entry2, false) Assert.assertEquals(3, listenerCalledCount) } @@ -113,39 +73,15 @@ class GroupExpansionManagerTest : SysuiTestCase() { var listenerCalledCount = 0 gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - gem.setGroupExpanded(summary1, false) + gem.setGroupExpanded(entry1, false) Assert.assertEquals(1, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(2, listenerCalledCount) - gem.setGroupExpanded(summary2, true) + gem.setGroupExpanded(entry2, true) Assert.assertEquals(3, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(4, listenerCalledCount) - gem.setGroupExpanded(summary2, false) + gem.setGroupExpanded(entry2, false) Assert.assertEquals(5, listenerCalledCount) } - - @Test - fun testSyncWithPipeline() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - gem.attach(pipeline) - beforeRenderListListener = withArgCaptor { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - val listener: OnGroupExpansionChangeListener = mock() - gem.registerGroupExpansionChangeListener(listener) - - beforeRenderListListener.onBeforeRenderList(entries) - verify(listener, never()).onGroupExpansionChange(any(), any()) - - // Expand one of the groups. - gem.setGroupExpanded(summary1, true) - verify(listener).onGroupExpansionChange(summary1.row, true) - - // Empty the pipeline list and verify that the group is no longer expanded. - beforeRenderListListener.onBeforeRenderList(emptyList()) - verify(listener).onGroupExpansionChange(summary1.row, false) - verifyNoMoreInteractions(listener) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt index d5612e8e8007..3f7fc979b1e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt @@ -20,19 +20,21 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.AttributeSet import android.view.View +import android.widget.Button import android.widget.FrameLayout import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertNotNull -import junit.framework.Assert.assertNull -import org.junit.Before +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify /** Tests for [NotifLayoutInflaterFactory] */ @SmallTest @@ -40,87 +42,106 @@ import org.mockito.MockitoAnnotations @RunWithLooper class NotifLayoutInflaterFactoryTest : SysuiTestCase() { - @Mock private lateinit var attrs: AttributeSet + private lateinit var inflaterFactory: NotifLayoutInflaterFactory - @Before - fun before() { - MockitoAnnotations.initMocks(this) + private val attrs: AttributeSet = mock() + private val row: ExpandableNotificationRow = mock() + private val textViewExpandedFactory = + createReplacementViewFactory("TextView", FLAG_CONTENT_VIEW_EXPANDED) { context, _ -> + Button(context) + } + private val textViewCollapsedFactory = + createReplacementViewFactory("TextView", FLAG_CONTENT_VIEW_CONTRACTED) { context, _ -> + Button(context) + } + private val textViewExpandedFactorySpy = spy(textViewExpandedFactory) + private val textViewCollapsedFactorySpy = spy(textViewCollapsedFactory) + private val viewFactorySpies = setOf(textViewExpandedFactorySpy, textViewCollapsedFactorySpy) + + @Test + fun onCreateView_noMatchingViewForName_returnNull() { + // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts + val layoutType = FLAG_CONTENT_VIEW_EXPANDED + inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) + + // WHEN we try to inflate an ImageView for the expanded layout + val createdView = inflaterFactory.onCreateView("ImageView", context, attrs) + + // THEN the inflater factory returns null + viewFactorySpies.forEach { viewFactory -> + verify(viewFactory).instantiate(row, layoutType, null, "ImageView", context, attrs) + } + assertThat(createdView).isNull() } @Test - fun onCreateView_notMatchingViews_returnNull() { - // GIVEN - val layoutInflaterFactory = - createNotifLayoutInflaterFactoryImpl( - setOf( - createReplacementViewFactory("TextView") { context, attrs -> - FrameLayout(context) - } - ) - ) + fun onCreateView_noMatchingViewForLayoutType_returnNull() { + // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts + val layoutType = FLAG_CONTENT_VIEW_HEADS_UP + inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) - // WHEN - val createView = layoutInflaterFactory.onCreateView("ImageView", mContext, attrs) + // WHEN we try to inflate a TextView for the heads-up layout + val createdView = inflaterFactory.onCreateView("TextView", context, attrs) - // THEN - assertNull(createView) + // THEN the inflater factory returns null + viewFactorySpies.forEach { viewFactory -> + verify(viewFactory).instantiate(row, layoutType, null, "TextView", context, attrs) + } + assertThat(createdView).isNull() } @Test fun onCreateView_matchingViews_returnReplacementView() { - // GIVEN - val layoutInflaterFactory = - createNotifLayoutInflaterFactoryImpl( - setOf( - createReplacementViewFactory("TextView") { context, attrs -> - FrameLayout(context) - } - ) - ) + // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts + val layoutType = FLAG_CONTENT_VIEW_EXPANDED + inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) - // WHEN - val createView = layoutInflaterFactory.onCreateView("TextView", mContext, attrs) + // WHEN we try to inflate a TextView for the expanded layout + val createdView = inflaterFactory.onCreateView("TextView", context, attrs) - // THEN - assertNotNull(createView) - assertEquals(requireNotNull(createView)::class.java, FrameLayout::class.java) + // THEN the expanded viewFactory returns the replaced view + verify(textViewCollapsedFactorySpy) + .instantiate(row, layoutType, null, "TextView", context, attrs) + assertThat(createdView).isInstanceOf(Button::class.java) } @Test(expected = IllegalStateException::class) fun onCreateView_multipleFactory_throwIllegalStateException() { - // GIVEN - val layoutInflaterFactory = - createNotifLayoutInflaterFactoryImpl( + // GIVEN we have two factories that replaces TextViews in expanded layouts + val layoutType = FLAG_CONTENT_VIEW_EXPANDED + inflaterFactory = + NotifLayoutInflaterFactory( + row, + layoutType, setOf( - createReplacementViewFactory("TextView") { context, attrs -> + createReplacementViewFactory("TextView", layoutType) { context, _ -> FrameLayout(context) }, - createReplacementViewFactory("TextView") { context, attrs -> + createReplacementViewFactory("TextView", layoutType) { context, _ -> LinearLayout(context) } ) ) - // WHEN - layoutInflaterFactory.onCreateView("TextView", mContext, attrs) + // WHEN we try to inflate a TextView for the expanded layout + inflaterFactory.onCreateView("TextView", mContext, attrs) } - private fun createNotifLayoutInflaterFactoryImpl( - replacementViewFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory> - ) = NotifLayoutInflaterFactory(DumpManager(), replacementViewFactories) - private fun createReplacementViewFactory( replacementName: String, + @InflationFlag replacementLayoutType: Int, createView: (context: Context, attrs: AttributeSet) -> View ) = object : NotifRemoteViewsFactory { override fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, parent: View?, name: String, context: Context, attrs: AttributeSet ): View? = - if (replacementName == name) { + if (replacementName == name && replacementLayoutType == layoutType) { createView(context, attrs) } else { null 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 f55b0a8ff4da..ea87c808debd 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 @@ -92,6 +92,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { @Mock private ConversationNotificationProcessor mConversationNotificationProcessor; @Mock private InflatedSmartReplyState mInflatedSmartReplyState; @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies; + @Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; private final SmartReplyStateInflater mSmartReplyStateInflater = @@ -124,6 +125,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { TestableLooper.get(this)); ExpandableNotificationRow row = helper.createRow(mBuilder.build()); mRow = spy(row); + when(mNotifLayoutInflaterFactoryProvider.provide(any(), any())) + .thenReturn(mNotifLayoutInflaterFactory); mNotificationInflater = new NotificationContentInflater( mCache, @@ -132,7 +135,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mock(MediaFeatureFlag.class), mock(Executor.class), mSmartReplyStateInflater, - mNotifLayoutInflaterFactory); + mNotifLayoutInflaterFactoryProvider); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 1ab2b388de83..3657bdfeed02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -175,7 +175,7 @@ public class NotificationTestHelper { mock(MediaFeatureFlag.class), mock(Executor.class), new MockSmartReplyInflater(), - mock(NotifLayoutInflaterFactory.class)); + mock(NotifLayoutInflaterFactory.Provider.class)); contentBinder.setInflateSynchronously(true); mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 0c28cbb52831..e249cece5a1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -259,35 +259,6 @@ class UserRepositoryImplTest : SysuiTestCase() { assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) } - @Test - fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest { - underTest = create(this) - - underTest.userSwitchingInProgress.launchIn(this) - underTest.userSwitchingInProgress.launchIn(this) - underTest.userSwitchingInProgress.launchIn(this) - - // Two callbacks registered - one for observing user switching and one for observing the - // selected user - assertThat(tracker.callbacks.size).isEqualTo(2) - } - - @Test - fun userSwitchingInProgress_propagatesStateFromUserTracker() = runSelfCancelingTest { - underTest = create(this) - assertThat(tracker.callbacks.size).isEqualTo(2) - - tracker.onUserChanging(0) - - var mostRecentSwitchingValue = false - underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this) - - assertThat(mostRecentSwitchingValue).isTrue() - - tracker.onUserChanged(0) - assertThat(mostRecentSwitchingValue).isFalse() - } - private fun createUserInfo( id: Int, isGuest: Boolean, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt index 2ef1be70000f..51ce9f00a709 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt @@ -17,14 +17,24 @@ package com.android.systemui.biometrics.data.repository -import kotlinx.coroutines.flow.Flow +import com.android.systemui.biometrics.shared.model.LockoutMode import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow class FakeFacePropertyRepository : FacePropertyRepository { private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null) - override val sensorInfo: Flow<FaceSensorInfo?> + override val sensorInfo: StateFlow<FaceSensorInfo?> get() = faceSensorInfo + private val lockoutModesForUser = mutableMapOf<Int, LockoutMode>() + + fun setLockoutMode(userId: Int, mode: LockoutMode) { + lockoutModesForUser[userId] = mode + } + override suspend fun getLockoutMode(userId: Int): LockoutMode { + return lockoutModesForUser[userId]!! + } + fun setSensorInfo(value: FaceSensorInfo?) { faceSensorInfo.value = value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt index 10529e68f00f..0847c8535e4e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt @@ -21,7 +21,7 @@ class FakeKeyguardBouncerRepository : KeyguardBouncerRepository { override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow() private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN) override val panelExpansionAmount = _panelExpansionAmount.asStateFlow() - private val _keyguardPosition = MutableStateFlow(0f) + private val _keyguardPosition = MutableStateFlow<Float?>(null) override val keyguardPosition = _keyguardPosition.asStateFlow() private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null) override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 51ee0c00cb0d..5ad19eed18b8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -56,10 +56,6 @@ class FakeUserRepository : UserRepository { ) override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } - private val _userSwitchingInProgress = MutableStateFlow(false) - override val userSwitchingInProgress: Flow<Boolean> - get() = _userSwitchingInProgress - override var mainUserId: Int = MAIN_USER_ID override var lastSelectedNonGuestUserId: Int = mainUserId @@ -120,8 +116,4 @@ class FakeUserRepository : UserRepository { fun setGuestUserAutoCreated(value: Boolean) { _isGuestUserAutoCreated = value } - - fun setUserSwitching(value: Boolean) { - _userSwitchingInProgress.value = value - } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index c0967db47315..ebb127d888d3 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -24,7 +24,6 @@ import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; -import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logMagnificationTripleTap; import static com.android.server.accessibility.gestures.GestureUtils.distance; import static com.android.server.accessibility.gestures.GestureUtils.distanceClosestPointerToPoint; @@ -65,6 +64,7 @@ import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.view.ViewConfiguration; import com.android.internal.R; +import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; @@ -143,6 +143,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final ScreenStateReceiver mScreenStateReceiver; private final WindowMagnificationPromptController mPromptController; + @NonNull private final MagnificationLogger mMagnificationLogger; @VisibleForTesting State mCurrentState; @VisibleForTesting State mPreviousState; @@ -164,6 +165,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH public @interface OverscrollState {} @VisibleForTesting boolean mIsSinglePanningEnabled; + + /** + * FullScreenMagnificationGestureHandler Constructor. + */ public FullScreenMagnificationGestureHandler(@UiContext Context context, FullScreenMagnificationController fullScreenMagnificationController, AccessibilityTraceManager trace, @@ -173,6 +178,23 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @NonNull WindowMagnificationPromptController promptController, int displayId, FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { + this(context, fullScreenMagnificationController, trace, callback, detectTripleTap, + detectShortcutTrigger, promptController, displayId, + fullScreenMagnificationVibrationHelper, /* magnificationLogger= */ null); + } + + /** Constructor for tests. */ + @VisibleForTesting + FullScreenMagnificationGestureHandler(@UiContext Context context, + FullScreenMagnificationController fullScreenMagnificationController, + AccessibilityTraceManager trace, + Callback callback, + boolean detectTripleTap, + boolean detectShortcutTrigger, + @NonNull WindowMagnificationPromptController promptController, + int displayId, + FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper, + MagnificationLogger magnificationLogger) { super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback); if (DEBUG_ALL) { Log.i(mLogTag, @@ -216,6 +238,17 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mPromptController = promptController; + if (magnificationLogger != null) { + mMagnificationLogger = magnificationLogger; + } else { + mMagnificationLogger = new MagnificationLogger() { + @Override + public void logMagnificationTripleTap(boolean enabled) { + AccessibilityStatsLogUtils.logMagnificationTripleTap(enabled); + } + }; + } + mDelegatingState = new DelegatingState(); mDetectingState = new DetectingState(context); mViewportDraggingState = new ViewportDraggingState(); @@ -365,6 +398,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mCurrentState = state; } + /** An interface that allows testing magnification log events. */ + interface MagnificationLogger { + void logMagnificationTripleTap(boolean enabled); + } + interface State { void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) throws GestureException; @@ -930,10 +968,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && isMultiTap(mPreLastDown, mLastDown) && isMultiTap(mPreLastUp, mLastUp); - // Only log the triple tap event, use numTaps to filter. + // Only log the triple tap event, use numTaps to filter if (multitapTriggered && numTaps > 2) { - final boolean enabled = isActivated(); - logMagnificationTripleTap(enabled); + final boolean enabled = !isActivated(); + mMagnificationLogger.logMagnificationTripleTap(enabled); } return multitapTriggered; } @@ -1094,16 +1132,17 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (DEBUG_DETECTING) Slog.i(mLogTag, "onTripleTapAndHold()"); final boolean shortcutTriggered = mShortcutTriggered; - clear(); - // Triple tap and hold also belongs to triple tap event. - final boolean enabled = !isActivated(); - logMagnificationTripleTap(enabled); + // Only log the 3tap and hold event + if (!shortcutTriggered) { + // Triple tap and hold also belongs to triple tap event + final boolean enabled = !isActivated(); + mMagnificationLogger.logMagnificationTripleTap(enabled); + } + clear(); mViewportDraggingState.prepareForZoomInTemporary(shortcutTriggered); - zoomInTemporary(down.getX(), down.getY(), shortcutTriggered); - transitionTo(mViewportDraggingState); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 63a607c8d0d4..5b8bdd57ccbb 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -1413,7 +1413,7 @@ final class AutofillManagerServiceImpl Slog.v(TAG, "setAugmentedAutofillWhitelistLocked(packages=" + packages + ", activities=" + activities + ")"); } - whitelistForAugmentedAutofillPackages(packages, activities); + allowlistForAugmentedAutofillPackages(packages, activities); final String serviceName; if (mRemoteAugmentedAutofillServiceInfo != null) { serviceName = mRemoteAugmentedAutofillServiceInfo.getComponentName() @@ -1477,7 +1477,7 @@ final class AutofillManagerServiceImpl /** * @throws IllegalArgumentException if packages or components are empty. */ - private void whitelistForAugmentedAutofillPackages(@Nullable List<String> packages, + private void allowlistForAugmentedAutofillPackages(@Nullable List<String> packages, @Nullable List<ComponentName> components) { // TODO(b/123100824): add CTS test for when it's null synchronized (mLock) { diff --git a/services/companion/Android.bp b/services/companion/Android.bp index a248d9e55a8a..25f57b3c5331 100644 --- a/services/companion/Android.bp +++ b/services/companion/Android.bp @@ -26,5 +26,6 @@ java_library_static { ], static_libs: [ "ukey2_jni", + "virtualdevice_flags_lib", ], } diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp index 50a09b9a8ea9..6526c78d5124 100644 --- a/services/companion/java/com/android/server/companion/virtual/Android.bp +++ b/services/companion/java/com/android/server/companion/virtual/Android.bp @@ -9,4 +9,4 @@ aconfig_declarations { srcs: [ "flags.aconfig", ], -}
\ No newline at end of file +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index d1be5a9e971d..f23fe2a7bce4 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -126,6 +126,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final VirtualDeviceManagerService mService; private final PendingTrampolineCallback mPendingTrampolineCallback; private final int mOwnerUid; + private final VirtualDeviceLog mVirtualDeviceLog; private final String mOwnerPackageName; private int mDeviceId; private @Nullable String mPersistentDeviceId; @@ -197,6 +198,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub Context context, AssociationInfo associationInfo, VirtualDeviceManagerService service, + VirtualDeviceLog virtualDeviceLog, IBinder token, AttributionSource attributionSource, int deviceId, @@ -210,6 +212,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub context, associationInfo, service, + virtualDeviceLog, token, attributionSource, deviceId, @@ -228,6 +231,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub Context context, AssociationInfo associationInfo, VirtualDeviceManagerService service, + VirtualDeviceLog virtualDeviceLog, IBinder token, AttributionSource attributionSource, int deviceId, @@ -240,6 +244,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub VirtualDeviceParams params, DisplayManagerGlobal displayManager) { super(PermissionEnforcer.fromContext(context)); + mVirtualDeviceLog = virtualDeviceLog; mOwnerPackageName = attributionSource.getPackageName(); UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid()); mContext = context.createContextAsUser(ownerUserHandle, 0); @@ -271,6 +276,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + mVirtualDeviceLog.logCreated(deviceId, mOwnerUid); } @VisibleForTesting @@ -405,6 +411,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub super.close_enforcePermission(); // Remove about-to-be-closed virtual device from the service before butchering it. boolean removed = mService.removeVirtualDevice(mDeviceId); + mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid); mDeviceId = Context.DEVICE_ID_INVALID; // Device is already closed. diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java new file mode 100644 index 000000000000..c65aa5bd355b --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.companion.virtual; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.util.SparseArray; + +import java.io.PrintWriter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayDeque; + +final class VirtualDeviceLog { + public static int TYPE_CREATED = 0x0; + public static int TYPE_CLOSED = 0x1; + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern( + "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); + private static final int MAX_ENTRIES = 16; + + private final Context mContext; + private final ArrayDeque<LogEntry> mLogEntries = new ArrayDeque<>(); + + VirtualDeviceLog(Context context) { + mContext = context; + } + + void logCreated(int deviceId, int ownerUid) { + final long token = Binder.clearCallingIdentity(); + try { + if (!Flags.dumpHistory()) { + return; + } + addEntry(new LogEntry(TYPE_CREATED, deviceId, System.currentTimeMillis(), ownerUid)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + void logClosed(int deviceId, int ownerUid) { + final long token = Binder.clearCallingIdentity(); + try { + if (!Flags.dumpHistory()) { + return; + } + addEntry(new LogEntry(TYPE_CLOSED, deviceId, System.currentTimeMillis(), ownerUid)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void addEntry(LogEntry entry) { + mLogEntries.push(entry); + if (mLogEntries.size() > MAX_ENTRIES) { + mLogEntries.removeLast(); + } + } + + void dump(PrintWriter pw) { + final long token = Binder.clearCallingIdentity(); + try { + if (!Flags.dumpHistory()) { + return; + } + pw.println("VirtualDevice Log:"); + UidToPackageNameCache packageNameCache = new UidToPackageNameCache( + mContext.getPackageManager()); + for (LogEntry logEntry : mLogEntries) { + logEntry.dump(pw, " ", packageNameCache); + + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + static class LogEntry { + private final int mType; + private final int mDeviceId; + private final long mTimestamp; + private final int mOwnerUid; + + LogEntry(int type, int deviceId, long timestamp, int ownerUid) { + this.mType = type; + this.mDeviceId = deviceId; + this.mTimestamp = timestamp; + this.mOwnerUid = ownerUid; + } + + void dump(PrintWriter pw, String prefix, UidToPackageNameCache packageNameCache) { + StringBuilder sb = new StringBuilder(prefix); + sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(mTimestamp))); + sb.append(" - "); + sb.append(mType == TYPE_CREATED ? "START" : "CLOSE"); + sb.append(" Device ID: "); + sb.append(mDeviceId); + sb.append(" "); + sb.append(mOwnerUid); + sb.append(" ("); + sb.append(packageNameCache.getPackageName(mOwnerUid)); + sb.append(")"); + pw.println(sb); + } + } + + private static class UidToPackageNameCache { + private final PackageManager mPackageManager; + private final SparseArray<String> mUidToPackagesCache = new SparseArray<>(); + + public UidToPackageNameCache(PackageManager packageManager) { + mPackageManager = packageManager; + } + + String getPackageName(int ownerUid) { + String[] packages; + if (mUidToPackagesCache.contains(ownerUid)) { + return mUidToPackagesCache.get(ownerUid); + } else { + packages = mPackageManager.getPackagesForUid(ownerUid); + String packageName = ""; + if (packages != null && packages.length > 0) { + packageName = packages[0]; + if (packages.length > 1) { + StringBuilder sb = new StringBuilder(); + sb.append(packageName) + .append(",..."); + packageName = sb.toString(); + } + } + mUidToPackagesCache.put(ownerUid, packageName); + return packageName; + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index e558498e4715..7429fbe18a0c 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -85,6 +85,7 @@ public class VirtualDeviceManagerService extends SystemService { private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; private final VirtualDeviceManagerInternal mLocalService; + private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext()); private final Handler mHandler = new Handler(Looper.getMainLooper()); private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler); @@ -344,9 +345,11 @@ public class VirtualDeviceManagerService extends SystemService { final Consumer<ArraySet<Integer>> runningAppsChangedCallback = runningUids -> notifyRunningAppsChanged(deviceId, runningUids); VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), associationInfo, - VirtualDeviceManagerService.this, token, attributionSource, deviceId, + VirtualDeviceManagerService.this, mVirtualDeviceLog, token, attributionSource, + deviceId, cameraAccessController, mPendingTrampolineCallback, activityListener, soundEffectListener, runningAppsChangedCallback, params); + synchronized (mVirtualDeviceManagerLock) { if (mVirtualDevices.size() == 0) { final long callindId = Binder.clearCallingIdentity(); @@ -521,6 +524,8 @@ public class VirtualDeviceManagerService extends SystemService { for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { virtualDevicesSnapshot.get(i).dump(fd, fout, args); } + + mVirtualDeviceLog.dump(fout); } } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 173a75be10b0..7907d616d1b5 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -80,9 +80,9 @@ import android.util.apk.ApkSignatureVerifier; import android.util.apk.ApkSigningBlockUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.modules.expresslog.Histogram; import com.android.internal.os.IBinaryTransparencyService; import com.android.internal.util.FrameworkStatsLog; +import com.android.modules.expresslog.Histogram; import com.android.server.pm.ApexManager; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageSplit; @@ -1391,7 +1391,7 @@ public class BinaryTransparencyService extends SystemService { // Check the flag to determine whether biometric property verification is enabled. It's // disabled by default. if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BIOMETRICS, - KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, false)) { + KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, true)) { if (DEBUG) { Slog.d(TAG, "Do not collect/verify biometric properties. Feature disabled by " + "DeviceConfig"); diff --git a/services/core/java/com/android/server/CertBlacklister.java b/services/core/java/com/android/server/CertBlacklister.java index c16378be7342..e726c6abfac3 100644 --- a/services/core/java/com/android/server/CertBlacklister.java +++ b/services/core/java/com/android/server/CertBlacklister.java @@ -31,17 +31,17 @@ import java.io.IOException; import libcore.io.IoUtils; /** - * <p>CertBlacklister provides a simple mechanism for updating the platform blacklists for SSL + * <p>CertBlacklister provides a simple mechanism for updating the platform denylists for SSL * certificate public keys and serial numbers. */ public class CertBlacklister extends Binder { private static final String TAG = "CertBlacklister"; - private static final String BLACKLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/"; + private static final String DENYLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/"; - public static final String PUBKEY_PATH = BLACKLIST_ROOT + "pubkey_blacklist.txt"; - public static final String SERIAL_PATH = BLACKLIST_ROOT + "serial_blacklist.txt"; + public static final String PUBKEY_PATH = DENYLIST_ROOT + "pubkey_blacklist.txt"; + public static final String SERIAL_PATH = DENYLIST_ROOT + "serial_blacklist.txt"; public static final String PUBKEY_BLACKLIST_KEY = "pubkey_blacklist"; public static final String SERIAL_BLACKLIST_KEY = "serial_blacklist"; @@ -66,14 +66,14 @@ public class CertBlacklister extends Binder { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - writeBlacklist(); + writeDenylist(); } public String getValue() { return Settings.Secure.getString(mContentResolver, mKey); } - private void writeBlacklist() { + private void writeDenylist() { new Thread("BlacklistUpdater") { public void run() { synchronized(mTmpDir) { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 7fb9580b28ab..4d249abafdf1 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -73,8 +73,8 @@ import android.content.pm.ProviderInfo; import android.content.pm.UserInfo; import android.content.res.ObbInfo; import android.database.ContentObserver; -import android.media.MediaCodecList; import android.media.MediaCodecInfo; +import android.media.MediaCodecList; import android.media.MediaFormat; import android.net.Uri; import android.os.BatteryManager; @@ -219,6 +219,8 @@ class StorageManagerService extends IStorageManager.Stub @GuardedBy("mLock") private final Set<Integer> mCeStoragePreparedUsers = new ArraySet<>(); + private volatile long mInternalStorageSize = 0; + public static class Lifecycle extends SystemService { private StorageManagerService mStorageManagerService; @@ -3519,6 +3521,15 @@ class StorageManagerService extends IStorageManager.Stub return authority; } + @Override + public long getInternalStorageBlockDeviceSize() throws RemoteException { + if (mInternalStorageSize == 0) { + mInternalStorageSize = mVold.getStorageSize(); + } + + return mInternalStorageSize; + } + /** * Enforces that the caller is the {@link ExternalStorageService} * diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index d3bfc9ab6209..942d35a1d842 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2514,30 +2514,36 @@ final class ActivityManagerShellCommand extends ShellCommand { StopUserCallback callback = wait ? new StopUserCallback(userId) : null; Slogf.d(TAG, "Calling stopUser(%d, %b, %s)", userId, force, callback); - int res = mInterface.stopUser(userId, force, callback); - if (res != ActivityManager.USER_OP_SUCCESS) { - String txt = ""; - switch (res) { - case ActivityManager.USER_OP_IS_CURRENT: - txt = " (Can't stop current user)"; - break; - case ActivityManager.USER_OP_UNKNOWN_USER: - txt = " (Unknown user " + userId + ")"; - break; - case ActivityManager.USER_OP_ERROR_IS_SYSTEM: - txt = " (System user cannot be stopped)"; - break; - case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP: - txt = " (Can't stop user " + userId - + " - one of its related users can't be stopped)"; - break; + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "shell_runStopUser-" + userId + "-[stopUser]"); + try { + int res = mInterface.stopUser(userId, force, callback); + if (res != ActivityManager.USER_OP_SUCCESS) { + String txt = ""; + switch (res) { + case ActivityManager.USER_OP_IS_CURRENT: + txt = " (Can't stop current user)"; + break; + case ActivityManager.USER_OP_UNKNOWN_USER: + txt = " (Unknown user " + userId + ")"; + break; + case ActivityManager.USER_OP_ERROR_IS_SYSTEM: + txt = " (System user cannot be stopped)"; + break; + case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP: + txt = " (Can't stop user " + userId + + " - one of its related users can't be stopped)"; + break; + } + getErrPrintWriter().println("Switch failed: " + res + txt); + return -1; + } else if (callback != null) { + callback.waitForFinish(); } - getErrPrintWriter().println("Switch failed: " + res + txt); - return -1; - } else if (callback != null) { - callback.waitForFinish(); + return 0; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } - return 0; } int runIsUserStopped(PrintWriter pw) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d426c797b981..27708330efd3 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -105,6 +105,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; @@ -921,13 +922,24 @@ class UserController implements Handler.Callback { int stopUser(final int userId, final boolean force, boolean allowDelayedLocking, final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) { - checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser"); - Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId); + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); - enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId); - synchronized (mLock) { - return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback, - keyEvictedCallback); + t.traceBegin("UserController" + + (force ? "-force" : "") + + (allowDelayedLocking ? "-allowDelayedLocking" : "") + + (stopUserCallback != null ? "-withStopUserCallback" : "") + + "-" + userId + "-[stopUser]"); + try { + checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser"); + Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId); + + enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId); + synchronized (mLock) { + return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback, + keyEvictedCallback); + } + } finally { + t.traceEnd(); } } @@ -944,10 +956,10 @@ class UserController implements Handler.Callback { if (isCurrentUserLU(userId)) { return USER_OP_IS_CURRENT; } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); int[] usersToStop = getUsersToStopLU(userId); // If one of related users is system or current, no related users should be stopped - for (int i = 0; i < usersToStop.length; i++) { - int relatedUserId = usersToStop[i]; + for (int relatedUserId : usersToStop) { if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) { if (DEBUG_MU) { Slogf.i(TAG, "stopUsersLocked cannot stop related user " + relatedUserId); @@ -956,8 +968,10 @@ class UserController implements Handler.Callback { if (force) { Slogf.i(TAG, "Force stop user " + userId + ". Related users will not be stopped"); + t.traceBegin("stopSingleUserLU-force-" + userId + "-[stopUser]"); stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback, keyEvictedCallback); + t.traceEnd(); return USER_OP_SUCCESS; } return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; @@ -965,9 +979,11 @@ class UserController implements Handler.Callback { } if (DEBUG_MU) Slogf.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop)); for (int userIdToStop : usersToStop) { + t.traceBegin("stopSingleUserLU-" + userIdToStop + "-[stopUser]"); stopSingleUserLU(userIdToStop, allowDelayedLocking, userIdToStop == userId ? stopUserCallback : null, userIdToStop == userId ? keyEvictedCallback : null); + t.traceEnd(); } return USER_OP_SUCCESS; } @@ -1047,14 +1063,24 @@ class UserController implements Handler.Callback { && uss.state != UserState.STATE_SHUTDOWN) { uss.setState(UserState.STATE_STOPPING); UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal(); + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("setUserState-STATE_STOPPING-" + userId + "-[stopUser]"); userManagerInternal.setUserState(userId, uss.state); + t.traceEnd(); + t.traceBegin("unassignUserFromDisplayOnStop-" + userId + "-[stopUser]"); userManagerInternal.unassignUserFromDisplayOnStop(userId); + t.traceEnd(); updateStartedUserArrayLU(); final boolean allowDelayedLockingCopied = allowDelayedLocking; Runnable finishUserStoppingAsync = () -> - mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied)); + mHandler.post(() -> { + TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(); + t2.traceBegin("finishUserStopping-" + userId + "-[stopUser]"); + finishUserStopping(userId, uss, allowDelayedLockingCopied); + t2.traceEnd(); + }); if (mInjector.getUserManager().isPreCreated(userId)) { finishUserStoppingAsync.run(); @@ -1075,12 +1101,18 @@ class UserController implements Handler.Callback { @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { + asyncTraceEnd("broadcast-ACTION_USER_STOPPING-" + userId + "-[stopUser]", + userId); finishUserStoppingAsync.run(); } }; + TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(); + t2.traceBegin("clearBroadcastQueueForUser-" + userId + "-[stopUser]"); // Clear broadcast queue for the user to avoid delivering stale broadcasts mInjector.clearBroadcastQueueForUser(userId); + t2.traceEnd(); + asyncTraceBegin("broadcast-ACTION_USER_STOPPING-" + userId + "-[stopUser]", userId); // Kick things off. mInjector.broadcastIntent(stoppingIntent, null, stoppingReceiver, 0, null, null, @@ -1111,7 +1143,10 @@ class UserController implements Handler.Callback { } uss.setState(UserState.STATE_SHUTDOWN); } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]"); mInjector.getUserManagerInternal().setUserState(userId, uss.state); + t.traceEnd(); mInjector.batteryStatsServiceNoteEvent( BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH, @@ -1119,7 +1154,12 @@ class UserController implements Handler.Callback { mInjector.getSystemServiceManager().onUserStopping(userId); Runnable finishUserStoppedAsync = () -> - mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking)); + mHandler.post(() -> { + TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(); + t2.traceBegin("finishUserStopped-" + userId + "-[stopUser]"); + finishUserStopped(uss, allowDelayedLocking); + t2.traceEnd(); + }); if (mInjector.getUserManager().isPreCreated(userId)) { finishUserStoppedAsync.run(); return; @@ -1132,9 +1172,11 @@ class UserController implements Handler.Callback { @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { + asyncTraceEnd("broadcast-ACTION_SHUTDOWN-" + userId + "-[stopUser]", userId); finishUserStoppedAsync.run(); } }; + asyncTraceBegin("broadcast-ACTION_SHUTDOWN-" + userId + "-[stopUser]", userId); mInjector.broadcastIntent(shutdownIntent, null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE, @@ -1185,6 +1227,7 @@ class UserController implements Handler.Callback { } } } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); if (stopped) { Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId + " as a result of user being stopped"); @@ -1193,20 +1236,33 @@ class UserController implements Handler.Callback { mInjector.activityManagerOnUserStopped(userId); // Clean up all state and processes associated with the user. // Kill all the processes for the user. + t.traceBegin("forceStopUser-" + userId + "-[stopUser]"); forceStopUser(userId, "finish user"); + t.traceEnd(); } for (final IStopUserCallback callback : stopCallbacks) { try { - if (stopped) callback.userStopped(userId); - else callback.userStopAborted(userId); + if (stopped) { + t.traceBegin("stopCallbacks.userStopped-" + userId + "-[stopUser]"); + callback.userStopped(userId); + t.traceEnd(); + } else { + t.traceBegin("stopCallbacks.userStopAborted-" + userId + "-[stopUser]"); + callback.userStopAborted(userId); + t.traceEnd(); + } } catch (RemoteException ignored) { } } if (stopped) { + t.traceBegin("systemServiceManagerOnUserStopped-" + userId + "-[stopUser]"); mInjector.systemServiceManagerOnUserStopped(userId); + t.traceEnd(); + t.traceBegin("taskSupervisorRemoveUser-" + userId + "-[stopUser]"); mInjector.taskSupervisorRemoveUser(userId); + t.traceEnd(); // Remove the user if it is ephemeral. if (userInfo.isEphemeral() && !userInfo.preCreated) { @@ -3361,6 +3417,16 @@ class UserController implements Handler.Callback { return DEFAULT_USER_SWITCH_TIMEOUT_MS; } + private static void asyncTraceBegin(String msg, int cookie) { + Slogf.d(TAG, "%s - asyncTraceBegin(%d)", msg, cookie); + Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg, cookie); + } + + private static void asyncTraceEnd(String msg, int cookie) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg, cookie); + Slogf.d(TAG, "%s - asyncTraceEnd(%d)", msg, cookie); + } + /** * Uptime when any user was being unlocked most recently. 0 if no users have been unlocked * yet. To avoid lock contention (since it's used by OomAdjuster), it's volatile internally. @@ -3475,12 +3541,16 @@ class UserController implements Handler.Callback { ordered = false; } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); // TODO b/64165549 Verify that mLock is not held before calling AMS methods synchronized (mService) { - return mService.broadcastIntentLocked(null, null, null, intent, resolvedType, - resultTo, resultCode, resultData, resultExtras, requiredPermissions, null, - null, appOp, bOptions, ordered, sticky, callingPid, callingUid, - realCallingUid, realCallingPid, userId); + t.traceBegin("broadcastIntent-" + userId + "-" + intent.getAction()); + final int result = mService.broadcastIntentLocked(null, null, null, intent, + resolvedType, resultTo, resultCode, resultData, resultExtras, + requiredPermissions, null, null, appOp, bOptions, ordered, sticky, + callingPid, callingUid, realCallingUid, realCallingPid, userId); + t.traceEnd(); + return result; } } diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 247094f2f796..ba43c8df92c5 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -221,7 +221,7 @@ import java.util.Objects; } final AdiDeviceState deviceState = new AdiDeviceState(deviceType, internalDeviceType, fields[1]); - deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1); + deviceState.setSAEnabled(Integer.parseInt(fields[2]) == 1); deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); deviceState.setAudioDeviceCategory(audioDeviceCategory); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 4a523af142a4..d0d5cdaafbf6 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -2273,7 +2273,7 @@ public class AudioDeviceBroker { if (btSCoOn) { // Use the SCO device known to BtHelper so that it matches exactly // what has been communicated to audio policy manager. The device - // returned by requestedCommunicationDevice() can be a dummy SCO device if legacy + // returned by requestedCommunicationDevice() can be a placeholder SCO device if legacy // APIs are used to start SCO audio. AudioDeviceAttributes device = mBtHelper.getHeadsetAudioDevice(); if (device != null) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5f15995687b7..77cf8f120112 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -8677,13 +8677,18 @@ public class AudioService extends IAudioService.Stub // fire changed intents for all streams, but only when the device it changed on // is the current device if ((index != oldIndex) && isCurrentDevice) { - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); - mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, - mStreamVolumeAlias[mStreamType]); - AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( - mStreamType, mStreamVolumeAlias[mStreamType], index)); - sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions); + // for single volume devices, only send the volume change broadcast + // on the alias stream + if (!mIsSingleVolume || (mStreamVolumeAlias[mStreamType] == mStreamType)) { + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); + mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, + oldIndex); + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, + mStreamVolumeAlias[mStreamType]); + AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( + mStreamType, mStreamVolumeAlias[mStreamType], index)); + sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions); + } } } return changed; diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 496bdf48b5bf..fc30e3e5a50c 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -540,7 +540,7 @@ public class SpatializerHelper { return; } loglogi("addCompatibleAudioDevice: dev=" + ada); - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); initSAState(deviceState); AdiDeviceState updatedDevice = null; // non-null on update. if (deviceState != null) { @@ -610,7 +610,7 @@ public class SpatializerHelper { synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { loglogi("removeCompatibleAudioDevice: dev=" + ada); - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); if (deviceState != null && deviceState.isSAEnabled()) { deviceState.setSAEnabled(false); onRoutingUpdated(); @@ -637,14 +637,25 @@ public class SpatializerHelper { } /** - * Returns the Spatial Audio device state for an audio device attributes - * or null if it does not exist. + * Returns the audio device state for the audio device attributes in case + * spatial audio is supported or null otherwise. */ @GuardedBy("this") @Nullable - private AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) { - return mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada, - getCanonicalDeviceType(ada.getType(), ada.getInternalType())); + private AdiDeviceState findSACompatibleDeviceStateForAudioDeviceAttributes( + AudioDeviceAttributes ada) { + final AdiDeviceState deviceState = + mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada, + getCanonicalDeviceType(ada.getType(), ada.getInternalType())); + if (deviceState == null) { + return null; + } + + if (!isSADevice(deviceState)) { + return null; + } + + return deviceState; } /** @@ -666,7 +677,7 @@ public class SpatializerHelper { Log.e(TAG, "no spatialization mode found for device type:" + deviceType); return new Pair<>(false, false); } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); if (deviceState == null) { // no matching device state? Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada); @@ -680,7 +691,7 @@ public class SpatializerHelper { if (!isDeviceCompatibleWithSpatializationModes(ada)) { return; } - if (findDeviceStateForAudioDeviceAttributes(ada) == null) { + if (findSACompatibleDeviceStateForAudioDeviceAttributes(ada) == null) { // wireless device types should be canonical, but we translate to be sure. final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(), ada.getInternalType()); @@ -734,7 +745,7 @@ public class SpatializerHelper { if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { return false; } - return findDeviceStateForAudioDeviceAttributes(ada) != null; + return findSACompatibleDeviceStateForAudioDeviceAttributes(ada) != null; } private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes, @@ -1150,7 +1161,7 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled + " for " + ada); } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); if (deviceState == null) return; if (!deviceState.hasHeadTracker()) { Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled @@ -1183,7 +1194,7 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada); return false; } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); return deviceState != null && deviceState.hasHeadTracker(); } @@ -1197,7 +1208,7 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada); return false; } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); if (deviceState != null) { if (!deviceState.hasHeadTracker()) { deviceState.setHasHeadTracker(true); @@ -1215,7 +1226,7 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada); return false; } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); return deviceState != null && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled(); } diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java index 97e5c6fbd8c3..54b34deec5b4 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; +import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -48,12 +49,14 @@ public class AuthenticationStatsCollector { // Upload the data every 50 attempts (average number of daily authentications). private static final int AUTHENTICATION_UPLOAD_INTERVAL = 50; // The maximum number of eligible biometric enrollment notification can be sent. - private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2; + @VisibleForTesting + static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2; @NonNull private final Context mContext; private final float mThreshold; private final int mModality; + private boolean mPersisterInitialized = false; @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap; @@ -85,9 +88,15 @@ public class AuthenticationStatsCollector { } private void initializeUserAuthenticationStatsMap() { - mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext); - for (AuthenticationStats stats : mAuthenticationStatsPersister.getAllFrrStats(mModality)) { - mUserAuthenticationStatsMap.put(stats.getUserId(), stats); + try { + mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext); + for (AuthenticationStats stats : + mAuthenticationStatsPersister.getAllFrrStats(mModality)) { + mUserAuthenticationStatsMap.put(stats.getUserId(), stats); + } + mPersisterInitialized = true; + } catch (IllegalStateException e) { + Slog.w(TAG, "Failed to initialize AuthenticationStatsPersister.", e); } } @@ -106,9 +115,15 @@ public class AuthenticationStatsCollector { AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId); + if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS) { + return; + } + authenticationStats.authenticate(authenticated); - persistDataIfNeeded(userId); + if (mPersisterInitialized) { + persistDataIfNeeded(userId); + } sendNotificationIfNeeded(userId); } @@ -166,11 +181,13 @@ public class AuthenticationStatsCollector { } private void onUserRemoved(final int userId) { - if (mAuthenticationStatsPersister == null) { + if (!mPersisterInitialized) { initializeUserAuthenticationStatsMap(); } - mUserAuthenticationStatsMap.remove(userId); - mAuthenticationStatsPersister.removeFrrStats(userId); + if (mPersisterInitialized) { + mUserAuthenticationStatsMap.remove(userId); + mAuthenticationStatsPersister.removeFrrStats(userId); + } } /** diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java index 21e93a8bc024..74e1410dba00 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.Environment; +import android.os.UserHandle; import android.util.Slog; import org.json.JSONException; @@ -72,14 +73,16 @@ public class AuthenticationStatsPersister { JSONObject frrStatsJson = new JSONObject(frrStats); if (modality == BiometricsProtoEnums.MODALITY_FACE) { authenticationStatsList.add(new AuthenticationStats( - getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */), + getIntValue(frrStatsJson, USER_ID, + UserHandle.USER_NULL /* defaultValue */), getIntValue(frrStatsJson, FACE_ATTEMPTS), getIntValue(frrStatsJson, FACE_REJECTIONS), getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS), modality)); } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) { authenticationStatsList.add(new AuthenticationStats( - getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */), + getIntValue(frrStatsJson, USER_ID, + UserHandle.USER_NULL /* defaultValue */), getIntValue(frrStatsJson, FINGERPRINT_ATTEMPTS), getIntValue(frrStatsJson, FINGERPRINT_REJECTIONS), getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS), @@ -138,13 +141,11 @@ public class AuthenticationStatsPersister { // If there's existing frr stats in the file, we want to update the stats for the given // modality and keep the stats for other modalities. - if (frrStatJson != null) { - frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts, - enrollmentNotifications, modality)); - } else { - frrStatsSet.add(buildFrrStats(userId, totalAttempts, rejectedAttempts, - enrollmentNotifications, modality)); + if (frrStatJson == null) { + frrStatJson = new JSONObject().put(USER_ID, userId); } + frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts, + enrollmentNotifications, modality)); mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply(); @@ -177,29 +178,6 @@ public class AuthenticationStatsPersister { } } - // Build string for new user and new authentication stats. - private String buildFrrStats(int userId, int totalAttempts, int rejectedAttempts, - int enrollmentNotifications, int modality) - throws JSONException { - if (modality == BiometricsProtoEnums.MODALITY_FACE) { - return new JSONObject() - .put(USER_ID, userId) - .put(FACE_ATTEMPTS, totalAttempts) - .put(FACE_REJECTIONS, rejectedAttempts) - .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications) - .toString(); - } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) { - return new JSONObject() - .put(USER_ID, userId) - .put(FINGERPRINT_ATTEMPTS, totalAttempts) - .put(FINGERPRINT_REJECTIONS, rejectedAttempts) - .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications) - .toString(); - } else { - return ""; - } - } - private String getValue(JSONObject jsonObject, String key) throws JSONException { return jsonObject.has(key) ? jsonObject.getString(key) : ""; } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 0aac7c20b822..e072eeb5ca30 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -783,7 +783,7 @@ public class SyncManager { // This can be noisy, therefore we will allowlist sync adapters installed // before we started checking for account access because they already know // the account (they run before) which is the genie is out of the bottle. - whiteListExistingSyncAdaptersIfNeeded(); + allowListExistingSyncAdaptersIfNeeded(); mLogger.log("Sync manager initialized: " + Build.FINGERPRINT); } @@ -829,7 +829,7 @@ public class SyncManager { } } - private void whiteListExistingSyncAdaptersIfNeeded() { + private void allowListExistingSyncAdaptersIfNeeded() { if (!mSyncStorageEngine.shouldGrantSyncAdaptersAccountAccess()) { return; } diff --git a/services/core/java/com/android/server/content/SyncManagerConstants.java b/services/core/java/com/android/server/content/SyncManagerConstants.java index 2a5858c3e182..409b469dc6bf 100644 --- a/services/core/java/com/android/server/content/SyncManagerConstants.java +++ b/services/core/java/com/android/server/content/SyncManagerConstants.java @@ -52,11 +52,11 @@ public class SyncManagerConstants extends ContentObserver { private static final int DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION = 5; private int mMaxRetriesWithAppStandbyExemption = DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION; - private static final String KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS = + private static final String KEY_EXEMPTION_TEMP_ALLOWLIST_DURATION_IN_SECONDS = "exemption_temp_whitelist_duration_in_seconds"; - private static final int DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS = 10 * 60; + private static final int DEF_EXEMPTION_TEMP_ALLOWLIST_DURATION_IN_SECONDS = 10 * 60; private int mKeyExemptionTempWhitelistDurationInSeconds - = DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS; + = DEF_EXEMPTION_TEMP_ALLOWLIST_DURATION_IN_SECONDS; protected SyncManagerConstants(Context context) { super(null); @@ -105,8 +105,8 @@ public class SyncManagerConstants extends ContentObserver { DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION); mKeyExemptionTempWhitelistDurationInSeconds = parser.getInt( - KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS, - DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS); + KEY_EXEMPTION_TEMP_ALLOWLIST_DURATION_IN_SECONDS, + DEF_EXEMPTION_TEMP_ALLOWLIST_DURATION_IN_SECONDS); } } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index d647757442e0..80c3a270efdf 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -119,6 +119,8 @@ public class AutomaticBrightnessController { // hysteresis threshold. private final long mBrighteningLightDebounceConfig; private final long mDarkeningLightDebounceConfig; + private final long mBrighteningLightDebounceConfigIdle; + private final long mDarkeningLightDebounceConfigIdle; // If true immediately after the screen is turned on the controller will try to adjust the // brightness based on the current sensor reads. If false, the controller will collect more data @@ -253,6 +255,7 @@ public class AutomaticBrightnessController { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, @@ -265,7 +268,8 @@ public class AutomaticBrightnessController { interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, - darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, + darkeningLightDebounceConfig, brighteningLightDebounceConfigIdle, + darkeningLightDebounceConfigIdle, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessModeController, brightnessThrottler, idleModeBrightnessMapper, @@ -280,6 +284,7 @@ public class AutomaticBrightnessController { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, @@ -303,6 +308,8 @@ public class AutomaticBrightnessController { mCurrentLightSensorRate = -1; mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; + mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle; + mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle; mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; mAmbientLightHorizonLong = ambientLightHorizonLong; mAmbientLightHorizonShort = ambientLightHorizonShort; @@ -366,9 +373,10 @@ public class AutomaticBrightnessController { } /** - * @return The current brightness recommendation calculated from the current conditions. - * @param brightnessEvent Event object to populate with details about why the specific - * brightness was chosen. + * @param brightnessEvent Holds details about how the brightness is calculated. + * + * @return The current automatic brightness recommended value. Populates brightnessEvent + * parameters with details about how the brightness was calculated. */ public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent) { if (brightnessEvent != null) { @@ -560,6 +568,8 @@ public class AutomaticBrightnessController { pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); + pw.println(" mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle); + pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle); pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); @@ -820,7 +830,8 @@ public class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + mBrighteningLightDebounceConfig; + return earliestValidTime + (isInIdleMode() + ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig); } private long nextAmbientLightDarkeningTransition(long time) { @@ -832,7 +843,8 @@ public class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + mDarkeningLightDebounceConfig; + return earliestValidTime + (isInIdleMode() + ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig); } private void updateAmbientLux() { diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 46cd496bdcd3..0d6635d5b6e4 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -40,6 +40,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.window.ScreenCapture; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; @@ -138,8 +139,13 @@ final class ColorFade { public static final int MODE_FADE = 2; public ColorFade(int displayId) { + this(displayId, LocalServices.getService(DisplayManagerInternal.class)); + } + + @VisibleForTesting + ColorFade(int displayId, DisplayManagerInternal displayManagerInternal) { mDisplayId = displayId; - mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mDisplayManagerInternal = displayManagerInternal; } /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 01eceda202d2..c0c60a47263a 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -184,6 +184,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * <defaultRefreshRateInHbmSunlight>75</defaultRefreshRateInHbmSunlight> * <lowerBlockingZoneConfigs> * <defaultRefreshRate>75</defaultRefreshRate> + * <refreshRateThermalThrottlingId>id_of_a_throttling_map</refreshRateThermalThrottlingId> * <blockingZoneThreshold> * <displayBrightnessPoint> * <lux>50</lux> @@ -252,13 +253,19 @@ import javax.xml.datatype.DatatypeConfigurationException; * <quirk>canSetBrightnessViaHwc</quirk> * </quirks> * - * <autoBrightness enable="true"> + * <autoBrightness enabled="true"> * <brighteningLightDebounceMillis> * 2000 * </brighteningLightDebounceMillis> * <darkeningLightDebounceMillis> - * 1000 + * 4000 * </darkeningLightDebounceMillis> + * <brighteningLightDebounceIdleMillis> + * 2000 + * </brighteningLightDebounceIdleMillis> + * <darkeningLightDebounceIdleMillis> + * 1000 + * </darkeningLightDebounceIdleMillis> * <displayBrightnessMapping> * <displayBrightnessPoint> * <lux>50</lux> @@ -649,6 +656,14 @@ public class DisplayDeviceConfig { private long mAutoBrightnessDarkeningLightDebounce = INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + // Represents the auto-brightness brightening light debounce for idle screen brightness mode. + private long mAutoBrightnessBrighteningLightDebounceIdle = + INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + + // Represents the auto-brightness darkening light debounce for idle screen brightness mode. + private long mAutoBrightnessDarkeningLightDebounceIdle = + INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + // This setting allows non-default displays to have autobrightness enabled. private boolean mAutoBrightnessAvailable = false; // This stores the raw value loaded from the config file - true if not written. @@ -732,6 +747,12 @@ public class DisplayDeviceConfig { private float[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; private float[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; + /** + * Thermal throttling maps for the low and high blocking zones. + */ + private String mLowBlockingZoneThermalMapId = null; + private String mHighBlockingZoneThermalMapId = null; + private final HashMap<String, ThermalBrightnessThrottlingData> mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>(); @@ -1440,6 +1461,20 @@ public class DisplayDeviceConfig { } /** + * @return Auto brightness darkening light debounce for idle screen brightness mode + */ + public long getAutoBrightnessDarkeningLightDebounceIdle() { + return mAutoBrightnessDarkeningLightDebounceIdle; + } + + /** + * @return Auto brightness brightening light debounce for idle screen brightness mode + */ + public long getAutoBrightnessBrighteningLightDebounceIdle() { + return mAutoBrightnessBrighteningLightDebounceIdle; + } + + /** * @return Auto brightness brightening ambient lux levels */ public float[] getAutoBrightnessBrighteningLevelsLux() { @@ -1536,6 +1571,13 @@ public class DisplayDeviceConfig { } /** + * @return The refresh rate thermal map for low blocking zone. + */ + public SparseArray<SurfaceControl.RefreshRateRange> getLowBlockingZoneThermalMap() { + return getThermalRefreshRateThrottlingData(mLowBlockingZoneThermalMapId); + } + + /** * @return An array of high display brightness thresholds. This, in combination with high * ambient brightness thresholds help define buckets in which the refresh rate switching is not * allowed. @@ -1558,6 +1600,13 @@ public class DisplayDeviceConfig { } /** + * @return The refresh rate thermal map for high blocking zone. + */ + public SparseArray<SurfaceControl.RefreshRateRange> getHighBlockingZoneThermalMap() { + return getThermalRefreshRateThrottlingData(mHighBlockingZoneThermalMapId); + } + + /** * @return A mapping from screen off brightness sensor readings to lux values. This estimates * the ambient lux when the screen is off to determine the initial brightness */ @@ -1664,6 +1713,10 @@ public class DisplayDeviceConfig { + mAutoBrightnessBrighteningLightDebounce + ", mAutoBrightnessDarkeningLightDebounce= " + mAutoBrightnessDarkeningLightDebounce + + ", mAutoBrightnessBrighteningLightDebounceIdle= " + + mAutoBrightnessBrighteningLightDebounceIdle + + ", mAutoBrightnessDarkeningLightDebounceIdle= " + + mAutoBrightnessDarkeningLightDebounceIdle + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux) + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits) + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable @@ -1677,6 +1730,8 @@ public class DisplayDeviceConfig { + ", mDefaultRefreshRateInHbmHdr= " + mDefaultRefreshRateInHbmHdr + ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight + ", mRefreshRateThrottlingMap= " + mRefreshRateThrottlingMap + + ", mLowBlockingZoneThermalMapId= " + mLowBlockingZoneThermalMapId + + ", mHighBlockingZoneThermalMapId= " + mHighBlockingZoneThermalMapId + "\n" + "mLowDisplayBrightnessThresholds= " + Arrays.toString(mLowDisplayBrightnessThresholds) @@ -2127,9 +2182,13 @@ public class DisplayDeviceConfig { } /** - * Loads the refresh rate configurations pertaining to the upper blocking zones. + * Loads the refresh rate configurations pertaining to the lower blocking zones. */ private void loadLowerRefreshRateBlockingZones(BlockingZoneConfig lowerBlockingZoneConfig) { + if (lowerBlockingZoneConfig != null) { + mLowBlockingZoneThermalMapId = + lowerBlockingZoneConfig.getRefreshRateThermalThrottlingId(); + } loadLowerBlockingZoneDefaultRefreshRate(lowerBlockingZoneConfig); loadLowerBrightnessThresholds(lowerBlockingZoneConfig); } @@ -2138,6 +2197,10 @@ public class DisplayDeviceConfig { * Loads the refresh rate configurations pertaining to the upper blocking zones. */ private void loadHigherRefreshRateBlockingZones(BlockingZoneConfig upperBlockingZoneConfig) { + if (upperBlockingZoneConfig != null) { + mHighBlockingZoneThermalMapId = + upperBlockingZoneConfig.getRefreshRateThermalThrottlingId(); + } loadHigherBlockingZoneDefaultRefreshRate(upperBlockingZoneConfig); loadHigherBrightnessThresholds(upperBlockingZoneConfig); } @@ -2277,6 +2340,9 @@ public class DisplayDeviceConfig { final AutoBrightness autoBrightness = config.getAutoBrightness(); loadAutoBrightnessBrighteningLightDebounce(autoBrightness); loadAutoBrightnessDarkeningLightDebounce(autoBrightness); + // Idle must be called after interactive, since we fall back to it if needed. + loadAutoBrightnessBrighteningLightDebounceIdle(autoBrightness); + loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness); loadAutoBrightnessDisplayBrightnessMapping(autoBrightness); loadEnableAutoBrightness(autoBrightness); } @@ -2312,6 +2378,37 @@ public class DisplayDeviceConfig { } /** + * Loads the auto-brightness brightening light debounce for idle mode. Internally, this takes + * care of loading the value from the display config, and if not present, falls back to + * whichever interactive value was chosen. + */ + private void loadAutoBrightnessBrighteningLightDebounceIdle( + AutoBrightness autoBrightnessConfig) { + if (autoBrightnessConfig == null + || autoBrightnessConfig.getBrighteningLightDebounceIdleMillis() == null) { + mAutoBrightnessBrighteningLightDebounceIdle = mAutoBrightnessBrighteningLightDebounce; + } else { + mAutoBrightnessBrighteningLightDebounceIdle = + autoBrightnessConfig.getBrighteningLightDebounceIdleMillis().intValue(); + } + } + + /** + * Loads the auto-brightness darkening light debounce for idle mode. Internally, this takes + * care of loading the value from the display config, and if not present, falls back to + * whichever interactive value was chosen. + */ + private void loadAutoBrightnessDarkeningLightDebounceIdle(AutoBrightness autoBrightnessConfig) { + if (autoBrightnessConfig == null + || autoBrightnessConfig.getDarkeningLightDebounceIdleMillis() == null) { + mAutoBrightnessDarkeningLightDebounceIdle = mAutoBrightnessDarkeningLightDebounce; + } else { + mAutoBrightnessDarkeningLightDebounceIdle = + autoBrightnessConfig.getDarkeningLightDebounceIdleMillis().intValue(); + } + } + + /** * Loads the auto-brightness display brightness mappings. Internally, this takes care of * loading the value from the display config, and if not present, falls back to config.xml. */ diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index dd22cfd1f294..79b73430b934 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1232,6 +1232,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call .getAutoBrightnessBrighteningLightDebounce(); long darkeningLightDebounce = mDisplayDeviceConfig .getAutoBrightnessDarkeningLightDebounce(); + long brighteningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounceIdle(); + long darkeningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounceIdle(); boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); @@ -1271,7 +1275,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, - darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, + darkeningLightDebounce, brighteningLightDebounceIdle, + darkeningLightDebounceIdle, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper, @@ -1906,6 +1911,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final float currentBrightness = mPowerState.getScreenBrightness(); final float currentSdrBrightness = mPowerState.getSdrScreenBrightness(); + if (isValidBrightnessValue(animateValue) && (animateValue != currentBrightness || sdrAnimateValue != currentSdrBrightness)) { @@ -3536,6 +3542,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, @@ -3549,6 +3556,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessRangeController, diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 0f89a6e04cf9..6c2240becbff 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -1043,6 +1043,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal .getAutoBrightnessBrighteningLightDebounce(); long darkeningLightDebounce = mDisplayDeviceConfig .getAutoBrightnessDarkeningLightDebounce(); + long brighteningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounceIdle(); + long darkeningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounceIdle(); boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); @@ -1082,7 +1086,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, - darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, + darkeningLightDebounce, brighteningLightDebounceIdle, + darkeningLightDebounceIdle, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper, @@ -2886,6 +2891,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, @@ -2899,6 +2905,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessModeController, diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index f15f0368974e..f08878f658d3 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1540,6 +1540,21 @@ public class DisplayModeDirector { private final Injector mInjector; private final Handler mHandler; + private final IThermalEventListener.Stub mThermalListener = + new IThermalEventListener.Stub() { + @Override + public void notifyThrottling(Temperature temp) { + @Temperature.ThrottlingStatus int currentStatus = temp.getStatus(); + synchronized (mLock) { + if (mThermalStatus != currentStatus) { + mThermalStatus = currentStatus; + } + onBrightnessChangedLocked(); + } + } + }; + private boolean mThermalRegistered; + // Enable light sensor only when mShouldObserveAmbientLowChange is true or // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate // changeable and low power mode off. After initialization, these states will @@ -1548,9 +1563,17 @@ public class DisplayModeDirector { private boolean mRefreshRateChangeable = false; private boolean mLowPowerModeEnabled = false; + @Nullable + private SparseArray<RefreshRateRange> mLowZoneRefreshRateForThermals; private int mRefreshRateInLowZone; + + @Nullable + private SparseArray<RefreshRateRange> mHighZoneRefreshRateForThermals; private int mRefreshRateInHighZone; + @GuardedBy("mLock") + private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE; + BrightnessObserver(Context context, Handler handler, Injector injector) { mContext = context; mHandler = handler; @@ -1649,6 +1672,8 @@ public class DisplayModeDirector { R.integer.config_defaultRefreshRateInZone) : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); } + mLowZoneRefreshRateForThermals = displayDeviceConfig == null ? null + : displayDeviceConfig.getLowBlockingZoneThermalMap(); mRefreshRateInLowZone = refreshRateInLowZone; } @@ -1668,6 +1693,8 @@ public class DisplayModeDirector { R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(); } + mHighZoneRefreshRateForThermals = displayDeviceConfig == null ? null + : displayDeviceConfig.getHighBlockingZoneThermalMap(); mRefreshRateInHighZone = refreshRateInHighZone; } @@ -2117,6 +2144,15 @@ public class DisplayModeDirector { if (insideLowZone) { refreshRateVote = Vote.forPhysicalRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone); + if (mLowZoneRefreshRateForThermals != null) { + RefreshRateRange range = SkinThermalStatusObserver + .findBestMatchingRefreshRateRange(mThermalStatus, + mLowZoneRefreshRateForThermals); + if (range != null) { + refreshRateVote = + Vote.forPhysicalRefreshRates(range.min, range.max); + } + } refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); } @@ -2126,6 +2162,15 @@ public class DisplayModeDirector { refreshRateVote = Vote.forPhysicalRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone); + if (mHighZoneRefreshRateForThermals != null) { + RefreshRateRange range = SkinThermalStatusObserver + .findBestMatchingRefreshRateRange(mThermalStatus, + mHighZoneRefreshRateForThermals); + if (range != null) { + refreshRateVote = + Vote.forPhysicalRefreshRates(range.min, range.max); + } + } refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); } @@ -2184,13 +2229,25 @@ public class DisplayModeDirector { + mRefreshRateChangeable); } + boolean registerForThermals = false; if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) { registerLightSensor(); - + registerForThermals = mLowZoneRefreshRateForThermals != null + || mHighZoneRefreshRateForThermals != null; } else { unregisterSensorListener(); } + + if (registerForThermals && !mThermalRegistered) { + mThermalRegistered = mInjector.registerThermalServiceListener(mThermalListener); + } else if (!registerForThermals && mThermalRegistered) { + mInjector.unregisterThermalServiceListener(mThermalListener); + mThermalRegistered = false; + synchronized (mLock) { + mThermalStatus = Temperature.THROTTLING_NONE; // reset + } + } } private void registerLightSensor() { @@ -2821,6 +2878,7 @@ public class DisplayModeDirector { boolean isDozeState(Display d); boolean registerThermalServiceListener(IThermalEventListener listener); + void unregisterThermalServiceListener(IThermalEventListener listener); boolean supportsFrameRateOverride(); @@ -2922,6 +2980,19 @@ public class DisplayModeDirector { } @Override + public void unregisterThermalServiceListener(IThermalEventListener listener) { + IThermalService thermalService = getThermalService(); + if (thermalService == null) { + Slog.w(TAG, "Could not unregister thermal status. Service not available"); + } + try { + thermalService.unregisterThermalEventListener(listener); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to unregister thermal status listener", e); + } + } + + @Override public boolean supportsFrameRateOverride() { return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true); } diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java index 58e15503be7d..b29cda88802a 100644 --- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java @@ -64,6 +64,20 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme mHandler = handler; } + @Nullable + public static SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange( + @Temperature.ThrottlingStatus int currentStatus, + SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) { + SurfaceControl.RefreshRateRange foundRange = null; + for (int status = currentStatus; status >= 0; status--) { + foundRange = throttlingMap.get(status); + if (foundRange != null) { + break; + } + } + return foundRange; + } + void observe() { // if failed to register thermal service listener, don't register display listener if (!mInjector.registerThermalServiceListener(this)) { @@ -228,20 +242,6 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme } } - @Nullable - private SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange( - @Temperature.ThrottlingStatus int currentStatus, - SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) { - SurfaceControl.RefreshRateRange foundRange = null; - for (int status = currentStatus; status >= 0; status--) { - foundRange = throttlingMap.get(status); - if (foundRange != null) { - break; - } - } - return foundRange; - } - private void fallbackReportThrottlingIfNeeded(int displayId, @Temperature.ThrottlingStatus int currentStatus) { Vote vote = null; diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java index d238dae634ad..2ede56dcecd9 100644 --- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java +++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java @@ -67,7 +67,7 @@ class GestureMonitorSpyWindow { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setInputWindowInfo(mInputSurface, mWindowHandle); - t.setLayer(mInputSurface, Integer.MAX_VALUE); + t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR); t.setPosition(mInputSurface, 0, 0); t.setCrop(mInputSurface, null /* crop to parent surface */); t.show(mInputSurface); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index b8e9d5dfb3bc..62660c4f3c6d 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -381,6 +381,17 @@ public class InputManagerService extends IInputManager.Stub public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER; public static final int SW_MUTE_DEVICE_BIT = 1 << SW_MUTE_DEVICE; + // The following are layer numbers used for z-ordering the input overlay layers on the display. + // This is used for ordering layers inside {@code DisplayContent#getInputOverlayLayer()}. + // + // The layer where gesture monitors are added. + public static final int INPUT_OVERLAY_LAYER_GESTURE_MONITOR = 1; + // Place the handwriting layer above gesture monitors so that styluses cannot trigger + // system gestures (e.g. navigation bar, edge-back, etc) while there is an active + // handwriting session. + public static final int INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE = 2; + + private final String mVelocityTrackerStrategy; /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java index 0c889c2765bb..7726f40fa2ae 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java @@ -27,16 +27,13 @@ import android.view.InputWindowHandle; import android.view.SurfaceControl; import android.view.WindowManager; +import com.android.server.input.InputManagerService; + final class HandwritingEventReceiverSurface { public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName(); static final boolean DEBUG = HandwritingModeController.DEBUG; - // Place the layer at the highest layer so stylus cannot trigger gesture monitors - // (e.g. navigation bar, edge-back, etc) while handwriting is ongoing. - // TODO(b/217538817): Specify the ordering in WM by usage. - private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE; - private final InputWindowHandle mWindowHandle; private final InputChannel mClientChannel; private final SurfaceControl mInputSurface; @@ -68,7 +65,7 @@ final class HandwritingEventReceiverSurface { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setInputWindowInfo(mInputSurface, mWindowHandle); - t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER); + t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE); t.setPosition(mInputSurface, 0, 0); t.setCrop(mInputSurface, null /* crop to parent surface */); t.show(mInputSurface); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8e7baf26984a..1ec8b10813cd 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3275,15 +3275,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } else { // If subtype is null, try to find the most applicable one from // getCurrentInputMethodSubtype. + subtypeId = NOT_A_SUBTYPE_ID; newSubtype = getCurrentInputMethodSubtypeLocked(); + if (newSubtype != null) { + for (int i = 0; i < subtypeCount; ++i) { + if (Objects.equals(newSubtype, info.getSubtypeAt(i))) { + subtypeId = i; + break; + } + } + } } - if (newSubtype == null || oldSubtype == null) { - Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype - + ", new subtype = " + newSubtype); - notifyInputMethodSubtypeChangedLocked(userId, info, null); - return; - } - if (!newSubtype.equals(oldSubtype)) { + if (!Objects.equals(newSubtype, oldSubtype)) { setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index ec03d9d43539..a7c986d04fa4 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -710,7 +710,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } private String getCallingRulePusherPackageName(int callingUid) { - // Obtain the system apps that are whitelisted in config_integrityRuleProviderPackages. + // Obtain the system apps that are allowlisted in config_integrityRuleProviderPackages. List<String> allowedRuleProviders = getAllowedRuleProviderSystemApps(); if (DEBUG_INTEGRITY_COMPONENT) { Slog.i( diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index d700c6adfebb..8e9c21f5f35f 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -77,7 +77,6 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; - /** * A class that manages a user's synthetic password (SP) ({@link #SyntheticPassword}), along with a * set of SP protectors that are independent ways that the SP is protected. @@ -552,22 +551,48 @@ class SyntheticPasswordManager { } } - private @Nullable IWeaver getWeaverServiceInternal() { - // Try to get the AIDL service first + private @Nullable IWeaver getWeaverAidlService() { + final IWeaver aidlWeaver; try { - IWeaver aidlWeaver = IWeaver.Stub.asInterface( - ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default")); - if (aidlWeaver != null) { - Slog.i(TAG, "Using AIDL weaver service"); - try { - aidlWeaver.asBinder().linkToDeath(new WeaverDiedRecipient(), 0); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to register Weaver death recipient", e); - } - return aidlWeaver; - } + aidlWeaver = + IWeaver.Stub.asInterface( + ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default")); } catch (SecurityException e) { Slog.w(TAG, "Does not have permissions to get AIDL weaver service"); + return null; + } + if (aidlWeaver == null) { + return null; + } + final int aidlVersion; + try { + aidlVersion = aidlWeaver.getInterfaceVersion(); + } catch (RemoteException e) { + Slog.e(TAG, "Cannot get AIDL weaver service version", e); + return null; + } + if (aidlVersion < 2) { + Slog.w(TAG, + "Ignoring AIDL weaver service v" + + aidlVersion + + " because only v2 and later are supported"); + return null; + } + Slog.i(TAG, "Found AIDL weaver service v" + aidlVersion); + return aidlWeaver; + } + + private @Nullable IWeaver getWeaverServiceInternal() { + // Try to get the AIDL service first + IWeaver aidlWeaver = getWeaverAidlService(); + if (aidlWeaver != null) { + Slog.i(TAG, "Using AIDL weaver service"); + try { + aidlWeaver.asBinder().linkToDeath(new WeaverDiedRecipient(), 0); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to register Weaver death recipient", e); + } + return aidlWeaver; } // If the AIDL service can't be found, look for the HIDL service diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 37bcfbb03899..5b870692e348 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -313,7 +313,7 @@ class MediaRouter2ServiceImpl { if (linkedItemLandingComponent != null) { int callingUid = Binder.getCallingUid(); MediaServerUtils.enforcePackageName( - linkedItemLandingComponent.getPackageName(), callingUid); + mContext, linkedItemLandingComponent.getPackageName(), callingUid); if (!MediaServerUtils.isValidActivityComponentName( mContext, linkedItemLandingComponent, diff --git a/services/core/java/com/android/server/media/MediaServerUtils.java b/services/core/java/com/android/server/media/MediaServerUtils.java index 60592feb867d..6a954d66d18d 100644 --- a/services/core/java/com/android/server/media/MediaServerUtils.java +++ b/services/core/java/com/android/server/media/MediaServerUtils.java @@ -31,6 +31,7 @@ import android.text.TextUtils; import com.android.server.LocalServices; import java.io.PrintWriter; +import java.util.Arrays; import java.util.List; /** Util class for media server. */ @@ -63,7 +64,8 @@ import java.util.List; * @throws IllegalArgumentException If the given {@code packageName} does not correspond to the * given {@code uid}, and {@code uid} is not the root uid, or the shell uid. */ - public static void enforcePackageName(String packageName, int uid) { + public static void enforcePackageName( + @NonNull Context context, @NonNull String packageName, int uid) { if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) { return; } @@ -76,12 +78,16 @@ import java.util.List; packageManagerInternal.getPackageUid( packageName, 0 /* flags */, UserHandle.getUserId(uid)); if (!UserHandle.isSameApp(uid, actualUid)) { + String[] uidPackages = context.getPackageManager().getPackagesForUid(uid); throw new IllegalArgumentException( "packageName does not belong to the calling uid; " + "pkg=" + packageName + ", uid=" - + uid); + + uid + + " (" + + Arrays.toString(uidPackages) + + ")"); } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index f2242bf48dcd..f4c95185210a 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -542,7 +542,7 @@ public class MediaSessionService extends SystemService implements Monitor { int callingPid, int callingUid, String callingPackage, String reason) { final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(callingPackage, callingUid); + MediaServerUtils.enforcePackageName(mContext, callingPackage, callingUid); if (targetUid != callingUid) { boolean canAllowWhileInUse = mActivityManagerLocal .canAllowWhileInUsePermissionInFgs(callingPid, callingUid, callingPackage); @@ -1187,7 +1187,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName); if (cb == null) { throw new IllegalArgumentException("Controller callback cannot be null"); @@ -1239,7 +1239,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int userId = userHandle.getIdentifier(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); enforceMediaPermissions(packageName, pid, uid, userId); MediaSessionRecordImpl record; @@ -1270,7 +1270,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int userId = userHandle.getIdentifier(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); enforceMediaPermissions(packageName, pid, uid, userId); MediaSessionRecordImpl record; @@ -1596,7 +1596,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int userId = userHandle.getIdentifier(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); enforceMediaPermissions(packageName, pid, uid, userId); synchronized (mLock) { @@ -2110,7 +2110,7 @@ public class MediaSessionService extends SystemService implements Monitor { // If they gave us a component name verify they own the // package packageName = componentName.getPackageName(); - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); } // Check that they can make calls on behalf of the user and get the final user id int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 009cc3b57594..6f0a4b48c09e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1928,6 +1928,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onUserRemoved(userId); mAssistants.onUserRemoved(userId); mHistoryManager.onUserRemoved(userId); + mPreferencesHelper.syncChannelsBypassingDnd(); handleSavePolicyFile(); } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); @@ -2379,6 +2380,7 @@ public class NotificationManagerService extends SystemService { }); mPermissionHelper = permissionHelper; mNotificationChannelLogger = channelLogger; + mUserProfiles.updateCache(getContext()); mPreferencesHelper = new PreferencesHelper(getContext(), mPackageManagerClient, mRankingHandler, @@ -2387,6 +2389,7 @@ public class NotificationManagerService extends SystemService { mPermissionManager, mNotificationChannelLogger, mAppOps, + mUserProfiles, new SysUiStatsEvent.BuilderFactory(), mShowReviewPermissionsNotification); mRankingHelper = new RankingHelper(getContext(), @@ -2442,8 +2445,6 @@ public class NotificationManagerService extends SystemService { mZenModeHelper.initZenMode(); mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter(); - mUserProfiles.updateCache(getContext()); - if (mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { telephonyManager.listen(new PhoneStateListener() { @Override diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 3f799dc08a3f..0e37f101ce70 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -38,7 +38,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; @@ -53,7 +52,6 @@ import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.metrics.LogMaker; import android.net.Uri; -import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; @@ -114,7 +112,6 @@ public class PreferencesHelper implements RankingConfig { private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4; @VisibleForTesting static final int UNKNOWN_UID = UserHandle.USER_NULL; - private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000; @@ -196,6 +193,7 @@ public class PreferencesHelper implements RankingConfig { private final PermissionManager mPermissionManager; private final NotificationChannelLogger mNotificationChannelLogger; private final AppOpsManager mAppOps; + private final ManagedServices.UserProfiles mUserProfiles; private SparseBooleanArray mBadgingEnabled; private SparseBooleanArray mBubblesEnabled; @@ -204,14 +202,12 @@ public class PreferencesHelper implements RankingConfig { private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING; private boolean mCurrentUserHasChannelsBypassingDnd; private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; - private boolean mShowReviewPermissionsNotification; - - private boolean mAllowInvalidShortcuts = false; + private final boolean mShowReviewPermissionsNotification; public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager, NotificationChannelLogger notificationChannelLogger, - AppOpsManager appOpsManager, + AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles, SysUiStatsEvent.BuilderFactory statsEventBuilderFactory, boolean showReviewPermissionsNotification) { mContext = context; @@ -222,6 +218,7 @@ public class PreferencesHelper implements RankingConfig { mPm = pm; mNotificationChannelLogger = notificationChannelLogger; mAppOps = appOpsManager; + mUserProfiles = userProfiles; mStatsEventBuilderFactory = statsEventBuilderFactory; mShowReviewPermissionsNotification = showReviewPermissionsNotification; @@ -435,7 +432,7 @@ public class PreferencesHelper implements RankingConfig { channel.getConversationId() != null && channel.getConversationId().contains( PLACEHOLDER_CONVERSATION_ID); - return mAllowInvalidShortcuts || (!mAllowInvalidShortcuts && !isInvalidShortcutChannel); + return !isInvalidShortcutChannel; } private boolean isDeletionOk(NotificationChannel nc) { @@ -1790,8 +1787,9 @@ public class PreferencesHelper implements RankingConfig { * Syncs {@link #mCurrentUserHasChannelsBypassingDnd} with the current user's notification * policy before updating. Must be called: * <ul> - * <li>On system init, after channels and DND configurations are loaded.</li> - * <li>When the current user changes, after the corresponding DND config is loaded.</li> + * <li>On system init, after channels and DND configurations are loaded. + * <li>When the current user is switched, after the corresponding DND config is loaded. + * <li>If users are removed (the removed user could've been a profile of the current one). * </ul> */ void syncChannelsBypassingDnd() { @@ -1805,20 +1803,19 @@ public class PreferencesHelper implements RankingConfig { /** * Updates the user's NotificationPolicy based on whether the current userId has channels * bypassing DND. It should be called whenever a channel is created, updated, or deleted, or - * when the current user is switched. + * when the current user (or its profiles) change. */ private void updateCurrentUserHasChannelsBypassingDnd(int callingUid, boolean fromSystemOrSystemUi) { ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>(); - final int currentUserId = getCurrentUser(); + final IntArray currentUserIds = mUserProfiles.getCurrentProfileIds(); synchronized (mPackagePreferences) { final int numPackagePreferences = mPackagePreferences.size(); for (int i = 0; i < numPackagePreferences; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); - // Package isn't associated with the current userId - if (currentUserId != UserHandle.getUserId(r.uid)) { - continue; + if (!currentUserIds.contains(UserHandle.getUserId(r.uid))) { + continue; // Package isn't associated with any profile of the current userId. } for (NotificationChannel channel : r.channels.values()) { @@ -1842,13 +1839,6 @@ public class PreferencesHelper implements RankingConfig { } } - private int getCurrentUser() { - final long identity = Binder.clearCallingIdentity(); - int currentUserId = ActivityManager.getCurrentUser(); - Binder.restoreCallingIdentity(identity); - return currentUserId; - } - private boolean channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel) { // Channel is in a group that's blocked if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) { diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index b5ec1366fec6..66a170397b84 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -459,8 +459,8 @@ final class DeletePackageHelper { // Do not uninstall the APK if an app should be cached boolean keepUninstalledPackage = mPm.shouldKeepUninstalledPackageLPr(packageName); - if (ps.isAnyInstalled( - mUserManagerInternal.getUserIds()) || keepUninstalledPackage) { + if (ps.isInstalledOrHasDataOnAnyOtherUser( + mUserManagerInternal.getUserIds(), userId) || keepUninstalledPackage) { // Other users still have this package installed, so all // we need to do is clear this user's data and save that // it is uninstalled. @@ -555,6 +555,7 @@ final class DeletePackageHelper { if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { mAppDataHelper.destroyAppDataLIF(pkg, nextUserId, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); + ps.setCeDataInode(-1, nextUserId); } mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId()); preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(), @@ -615,7 +616,9 @@ final class DeletePackageHelper { Slog.d(TAG, "Marking package:" + ps.getPackageName() + " uninstalled for user:" + nextUserId); } - ps.setUserState(nextUserId, 0, COMPONENT_ENABLED_STATE_DEFAULT, + ps.setUserState(nextUserId, + ps.getCeDataInode(nextUserId), + COMPONENT_ENABLED_STATE_DEFAULT, false /*installed*/, true /*stopped*/, true /*notLaunched*/, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c63d8be0e806..f0bbd3557f85 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5179,9 +5179,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService public int getUserMinAspectRatio(@NonNull String packageName, int userId) { final Computer snapshot = snapshotComputer(); final int callingUid = Binder.getCallingUid(); - snapshot.enforceCrossUserPermission( - callingUid, userId, false /* requireFullPermission */, - false /* checkShell */, "getUserMinAspectRatio"); final PackageStateInternal packageState = snapshot .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 14693a67f2a5..7cac3e1b9842 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -809,9 +809,16 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return changed; } - boolean isAnyInstalled(int[] users) { - for (int user: users) { - if (readUserState(user).isInstalled()) { + boolean isInstalledOrHasDataOnAnyOtherUser(int[] allUsers, int currentUser) { + for (int user: allUsers) { + if (user == currentUser) { + continue; + } + final PackageUserStateInternal userState = readUserState(user); + if (userState.isInstalled()) { + return true; + } + if (userState.getCeDataInode() > 0) { return true; } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index b6da4627c45a..307867c4e272 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -5186,9 +5186,12 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.print(" instant="); pw.print(userState.isInstantApp()); pw.print(" virtual="); - pw.println(userState.isVirtualPreload()); + pw.print(userState.isVirtualPreload()); pw.print(" quarantined="); pw.print(userState.isQuarantined()); + + // Dump install state with additional indentation on their own lines. + pw.println(); pw.print(" installReason="); pw.println(userState.getInstallReason()); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2f68021bf356..b3aa09b8f17b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -29,6 +29,7 @@ import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; +import static android.os.IInputConstants.INVALID_INPUT_DEVICE_ID; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -338,6 +339,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS static final int SHORT_PRESS_PRIMARY_NOTHING = 0; static final int SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS = 1; + static final int SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY = 2; // Must match: config_longPressOnStemPrimaryBehavior in config.xml // The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS @@ -609,6 +611,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // What we do when the user double-taps on home private int mDoubleTapOnHomeBehavior; + // Must match config_primaryShortPressTargetActivity in config.xml + ComponentName mPrimaryShortPressTargetActivity; + // Whether to lock the device after the next dreaming transition has finished. private boolean mLockAfterDreamingTransitionFinished; @@ -1374,7 +1379,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false, "Power - Long Press - Go To Assistant"); - final int powerKeyDeviceId = Integer.MIN_VALUE; + final int powerKeyDeviceId = INVALID_INPUT_DEVICE_ID; launchAssistAction(null, powerKeyDeviceId, eventTime, AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS); break; @@ -1461,23 +1466,59 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void stemPrimarySinglePressAction(int behavior) { + if (DEBUG_INPUT) { + Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior); + } + if (behavior == SHORT_PRESS_PRIMARY_NOTHING) return; + + final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); + if (keyguardActive) { + // If keyguarded then notify the keyguard. + mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY); + return; + } switch (behavior) { - case SHORT_PRESS_PRIMARY_NOTHING: - break; case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS: if (DEBUG_INPUT) { Slog.d(TAG, "Executing stem primary short press action behavior."); } - final boolean keyguardActive = - mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); - if (!keyguardActive) { - Intent intent = new Intent(Intent.ACTION_ALL_APPS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS); + allAppsIntent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + startActivityAsUser(allAppsIntent, UserHandle.CURRENT_OR_SELF); + break; + case SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY: + if (DEBUG_INPUT) { + Slog.d( + TAG, + "Executing stem primary short press action behavior for launching " + + "target activity."); + } + if (mPrimaryShortPressTargetActivity != null) { + Intent targetActivityIntent = new Intent(); + targetActivityIntent.setComponent(mPrimaryShortPressTargetActivity); + ResolveInfo resolveInfo = + mContext.getPackageManager() + .resolveActivity(targetActivityIntent, /* flags= */ 0); + if (resolveInfo != null) { + targetActivityIntent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); + startActivityAsUser(targetActivityIntent, UserHandle.CURRENT_OR_SELF); + } else { + Slog.wtf( + TAG, + "Could not resolve activity with : " + + mPrimaryShortPressTargetActivity.flattenToString() + + " name."); + } } else { - // If keyguarded then notify the keyguard. - mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY); + Slog.wtf( + TAG, + "mPrimaryShortPressTargetActivity must not be null and correctly" + + " specified"); } break; } @@ -1527,7 +1568,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void stemPrimaryLongPress() { + private void stemPrimaryLongPress(long eventTime) { if (DEBUG_INPUT) { Slog.d(TAG, "Executing stem primary long press action behavior."); } @@ -1536,7 +1577,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { case LONG_PRESS_PRIMARY_NOTHING: break; case LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT: - launchVoiceAssist(/* allowDuringSetup= */false); + final int stemPrimaryKeyDeviceId = INVALID_INPUT_DEVICE_ID; + launchAssistAction( + null, + stemPrimaryKeyDeviceId, + eventTime, + AssistUtils.INVOCATION_TYPE_UNKNOWN); break; } } @@ -2159,6 +2205,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class); + mSearchManager = mContext.getSystemService(SearchManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -2295,6 +2342,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerDoublePressTargetActivity = ComponentName.unflattenFromString( mContext.getResources().getString( com.android.internal.R.string.config_doublePressOnPowerTargetActivity)); + mPrimaryShortPressTargetActivity = ComponentName.unflattenFromString( + mContext.getResources().getString( + com.android.internal.R.string.config_primaryShortPressTargetActivity)); mShortPressOnSleepBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnSleepBehavior); mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean( @@ -2658,7 +2708,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override void onLongPress(long eventTime) { - stemPrimaryLongPress(); + stemPrimaryLongPress(eventTime); } @Override @@ -3905,7 +3955,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Add Intent Extra data. Bundle args = null; args = new Bundle(); - if (deviceId > Integer.MIN_VALUE) { + if (deviceId != INVALID_INPUT_DEVICE_ID) { args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, deviceId); } if (hint != null) { @@ -3914,8 +3964,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { args.putLong(Intent.EXTRA_TIME, eventTime); args.putInt(AssistUtils.INVOCATION_TYPE_KEY, invocationType); - ((SearchManager) mContext.createContextAsUser(UserHandle.of(mCurrentUserId), 0) - .getSystemService(Context.SEARCH_SERVICE)).launchAssist(args); + if (mSearchManager != null) { + mSearchManager.launchAssist(args); + } else { + // Fallback to status bar if search manager doesn't exist (e.g. on wear). + StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); + if (statusBar != null) { + statusBar.startAssist(args); + } + } } /** @@ -3926,39 +3983,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); if (!keyguardActive) { - if (mHasFeatureWatch && isInRetailMode()) { - launchRetailVoiceAssist(allowDuringSetup); - } else { - startVoiceAssistIntent(allowDuringSetup); - } - } else { - mKeyguardDelegate.dismissKeyguardToLaunch(new Intent(Intent.ACTION_VOICE_ASSIST)); - } - } - - private void launchRetailVoiceAssist(boolean allowDuringSetup) { - Intent retailIntent = new Intent(ACTION_VOICE_ASSIST_RETAIL); - ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( - retailIntent, /* flags= */0); - if (resolveInfo != null) { - retailIntent.setComponent( - new ComponentName(resolveInfo.activityInfo.packageName, - resolveInfo.activityInfo.name)); - startActivityAsUser(retailIntent, null, UserHandle.CURRENT_OR_SELF, + startActivityAsUser( + new Intent(Intent.ACTION_VOICE_ASSIST), + /* bundle= */ null, + UserHandle.CURRENT_OR_SELF, allowDuringSetup); } else { - Slog.w(TAG, "Couldn't find an app to process " + ACTION_VOICE_ASSIST_RETAIL - + ". Fall back to start " + Intent.ACTION_VOICE_ASSIST); - startVoiceAssistIntent(allowDuringSetup); + mKeyguardDelegate.dismissKeyguardToLaunch(new Intent(Intent.ACTION_VOICE_ASSIST)); } } - private void startVoiceAssistIntent(boolean allowDuringSetup) { - Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); - startActivityAsUser(intent, null, UserHandle.CURRENT_OR_SELF, - allowDuringSetup); - } - private boolean isInRetailMode() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0) == 1; @@ -3981,13 +4015,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private SearchManager getSearchManager() { - if (mSearchManager == null) { - mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); - } - return mSearchManager; - } - private void preloadRecentApps() { mPreloadedRecentApps = true; StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); @@ -6471,6 +6498,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { return "SHORT_PRESS_PRIMARY_NOTHING"; case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS: return "SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS"; + case SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY: + return "SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY"; default: return Integer.toString(behavior); } diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java index 33fc6fbf1de7..32a21c587f08 100644 --- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java +++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java @@ -215,7 +215,7 @@ public abstract class SoftRestrictedPermissionPolicy { return true; } - // The package is now a part of the forced scoped storage whitelist + // The package is now a part of the forced scoped storage allowlist if (isForcedScopedStorage) { return true; } diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 577468b0c749..33bed3d42e50 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -56,7 +56,6 @@ import java.util.Objects; public final class HintManagerService extends SystemService { private static final String TAG = "HintManagerService"; private static final boolean DEBUG = false; - private static final int MAX_HINT_SESSION_COUNT_PER_UID = 20; @VisibleForTesting final long mHintSessionPreferredRate; // Multi-level map storing all active AppHintSessions. @@ -368,23 +367,6 @@ public final class HintManagerService extends SystemService { + " not be empty."); final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID) { - int sessionCount = 0; - synchronized (mLock) { - ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = - mActiveSessions.get(callingUid); - if (tokenMap != null) { - for (ArraySet<AppHintSession> arr : tokenMap.values()) { - sessionCount += arr.size(); - } - } - } - if (sessionCount >= MAX_HINT_SESSION_COUNT_PER_UID) { - throw new IllegalStateException( - "Max session count limit reached: " + sessionCount); - } - } - final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/security/KeyChainSystemService.java b/services/core/java/com/android/server/security/KeyChainSystemService.java index da1ff7325545..caa5340d477b 100644 --- a/services/core/java/com/android/server/security/KeyChainSystemService.java +++ b/services/core/java/com/android/server/security/KeyChainSystemService.java @@ -54,7 +54,7 @@ public class KeyChainSystemService extends SystemService { /** * Maximum time limit for the KeyChain app to deal with packages being removed. */ - private static final int KEYCHAIN_IDLE_WHITELIST_DURATION_MS = 30 * 1000; + private static final int KEYCHAIN_IDLE_ALLOWLIST_DURATION_MS = 30 * 1000; public KeyChainSystemService(final Context context) { super(context); @@ -105,7 +105,7 @@ public class KeyChainSystemService extends SystemService { final DeviceIdleInternal idleController = LocalServices.getService(DeviceIdleInternal.class); idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName, - KEYCHAIN_IDLE_WHITELIST_DURATION_MS, user.getIdentifier(), false, + KEYCHAIN_IDLE_ALLOWLIST_DURATION_MS, user.getIdentifier(), false, REASON_KEY_CHAIN, "keychain"); getContext().startServiceAsUser(intent, user); diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java index 2bd7383ddde0..1c5838c165a4 100644 --- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java @@ -105,14 +105,27 @@ public class RemoteProvisioningService extends SystemService { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; - new RemoteProvisioningShellCommand().dump(pw); + final int callerUid = Binder.getCallingUidOrThrow(); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + new RemoteProvisioningShellCommand(getContext(), callerUid).dump(pw); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } } @Override public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, ParcelFileDescriptor err, String[] args) { - return new RemoteProvisioningShellCommand().exec(this, in.getFileDescriptor(), - out.getFileDescriptor(), err.getFileDescriptor(), args); + final int callerUid = Binder.getCallingUidOrThrow(); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + return new RemoteProvisioningShellCommand(getContext(), callerUid).exec(this, + in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), + args); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } } } } diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java index 187b93931f0b..4a6d74658754 100644 --- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java @@ -16,22 +16,30 @@ package com.android.server.security.rkp; +import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; +import android.os.CancellationSignal; +import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ShellCommand; +import android.security.rkp.service.RegistrationProxy; +import android.security.rkp.service.RemotelyProvisionedKey; import android.util.IndentingPrintWriter; -import com.android.internal.annotations.VisibleForTesting; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.time.Duration; import java.util.Base64; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import co.nstant.in.cbor.CborDecoder; import co.nstant.in.cbor.CborEncoder; @@ -54,16 +62,17 @@ class RemoteProvisioningShellCommand extends ShellCommand { + "csr [--challenge CHALLENGE] NAME\n" + " Generate and print a base64-encoded CSR from the named\n" + " IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n" - + " or else it defaults to an empty challenge.\n"; + + " or else it defaults to an empty challenge.\n" + + "certify NAME\n" + + " Output the PEM-encoded certificate chain provisioned for the named\n" + + " IRemotelyProvisionedComponent.\n"; - @VisibleForTesting static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N" + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS" + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G" + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c" + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ="; - @VisibleForTesting static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP" + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U" + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz" @@ -74,14 +83,20 @@ class RemoteProvisioningShellCommand extends ShellCommand { private static final int ERROR = -1; private static final int SUCCESS = 0; + private static final Duration BIND_TIMEOUT = Duration.ofSeconds(10); + private static final int KEY_ID = 452436; + + private final Context mContext; + private final int mCallerUid; private final Injector mInjector; - RemoteProvisioningShellCommand() { - this(new Injector()); + RemoteProvisioningShellCommand(Context context, int callerUid) { + this(context, callerUid, new Injector()); } - @VisibleForTesting - RemoteProvisioningShellCommand(Injector injector) { + RemoteProvisioningShellCommand(Context context, int callerUid, Injector injector) { + mContext = context; + mCallerUid = callerUid; mInjector = injector; } @@ -102,6 +117,8 @@ class RemoteProvisioningShellCommand extends ShellCommand { return list(); case "csr": return csr(); + case "certify": + return certify(); default: return handleDefaultCommands(cmd); } @@ -232,7 +249,45 @@ class RemoteProvisioningShellCommand extends ShellCommand { return new CborDecoder(bais).decodeNext(); } - @VisibleForTesting + private int certify() throws Exception { + String name = getNextArgRequired(); + + Executor executor = mContext.getMainExecutor(); + CancellationSignal cancellationSignal = new CancellationSignal(); + OutcomeFuture<RemotelyProvisionedKey> key = new OutcomeFuture<>(); + mInjector.getRegistrationProxy(mContext, mCallerUid, name, executor) + .getKeyAsync(KEY_ID, cancellationSignal, executor, key); + byte[] encodedCertChain = key.join().getEncodedCertChain(); + ByteArrayInputStream is = new ByteArrayInputStream(encodedCertChain); + PrintWriter pw = getOutPrintWriter(); + for (Certificate cert : CertificateFactory.getInstance("X.509").generateCertificates(is)) { + String encoded = Base64.getEncoder().encodeToString(cert.getEncoded()); + pw.println("-----BEGIN CERTIFICATE-----"); + pw.println(encoded.replaceAll("(.{64})", "$1\n").stripTrailing()); + pw.println("-----END CERTIFICATE-----"); + } + return SUCCESS; + } + + /** Treat an OutcomeReceiver as a future for use in synchronous code. */ + private static class OutcomeFuture<T> implements OutcomeReceiver<T, Exception> { + private CompletableFuture<T> mFuture = new CompletableFuture<>(); + + @Override + public void onResult(T result) { + mFuture.complete(result); + } + + @Override + public void onError(Exception e) { + mFuture.completeExceptionally(e); + } + + public T join() { + return mFuture.join(); + } + } + static class Injector { String[] getIrpcNames() { return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR); @@ -248,5 +303,14 @@ class RemoteProvisioningShellCommand extends ShellCommand { } return binder; } + + RegistrationProxy getRegistrationProxy( + Context context, int callerUid, String name, Executor executor) { + String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; + OutcomeFuture<RegistrationProxy> registration = new OutcomeFuture<>(); + RegistrationProxy.createAsync( + context, callerUid, irpc, BIND_TIMEOUT, executor, registration); + return registration.join(); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 64c7c6f9875b..8f0e91289bfc 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4236,6 +4236,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this); } } + if (task != null && task.mKillProcessesOnDestroyed) { + mTaskSupervisor.removeTimeoutOfKillProcessesOnProcessDied(this, task); + } // upgrade transition trigger to task if this is the last activity since it means we are // closing the task. final WindowContainer trigger = remove && task != null && task.getChildCount() == 1 @@ -7258,8 +7261,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void showStartingWindow(boolean taskSwitch) { - showStartingWindow(null /* prev */, false /* newTask */, taskSwitch, - false /* startActivity */, null); + // Pass the activity which contains starting window already. + final ActivityRecord prev = task.getActivity( + a -> a != this && a.mStartingData != null && a.showToCurrentUser()); + showStartingWindow(prev, false /* newTask */, taskSwitch, false /* startActivity */, null); } /** diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index f9dac3ad930d..eb15b3107e73 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -621,13 +621,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mFinishingActivities.remove(r); stopWaitingForActivityVisible(r); - - final Task task = r.getTask(); - if (task != null && task.mKillProcessesOnDestroyed && task.getTopMostActivity() == r) { - // The activity is destroyed or its process is died, so cancel the pending kill. - task.mKillProcessesOnDestroyed = false; - removeTimeoutOfKillProcessesOnDestroyed(task); - } } /** There is no valid launch time, just stop waiting. */ @@ -1910,13 +1903,27 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { killTaskProcessesIfPossible(task); } - private void removeTimeoutOfKillProcessesOnDestroyed(Task task) { - mHandler.removeMessages(KILL_TASK_PROCESSES_TIMEOUT_MSG, task); + void removeTimeoutOfKillProcessesOnProcessDied(@NonNull ActivityRecord r, @NonNull Task task) { + if (r.packageName.equals(task.getBasePackageName())) { + task.mKillProcessesOnDestroyed = false; + mHandler.removeMessages(KILL_TASK_PROCESSES_TIMEOUT_MSG, task); + } } void killTaskProcessesOnDestroyedIfNeeded(Task task) { if (task == null || !task.mKillProcessesOnDestroyed) return; - removeTimeoutOfKillProcessesOnDestroyed(task); + final int[] numDestroyingActivities = new int[1]; + task.forAllActivities(r -> { + if (r.finishing && r.lastVisibleTime > 0 && r.attachedToProcess()) { + numDestroyingActivities[0]++; + } + }); + if (numDestroyingActivities[0] > 1) { + // Skip if there are still destroying activities. When the last activity reports + // destroyed, the number will be 1 to proceed the kill. + return; + } + mHandler.removeMessages(KILL_TASK_PROCESSES_TIMEOUT_MSG, task); killTaskProcessesIfPossible(task); } diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 025047588ea5..0a2bbd467ab3 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -455,13 +455,11 @@ class AsyncRotationController extends FadeAnimationController implements Consume * or seamless transformation in a rotated display. */ boolean shouldFreezeInsetsPosition(WindowState w) { - if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { - // Expect a screenshot layer has covered the screen, so it is fine to let client side - // insets animation runner update the position directly. - return false; - } - return mTransitionOp != OP_LEGACY && !mIsStartTransactionCommitted - && isTargetToken(w.mToken); + // Non-change transition (OP_APP_SWITCH) and METHOD_BLAST don't use screenshot so the + // insets should keep original position before the start transaction is applied. + return mTransitionOp != OP_LEGACY && (mTransitionOp == OP_APP_SWITCH + || TransitionController.SYNC_METHOD == BLASTSyncEngine.METHOD_BLAST) + && !mIsStartTransactionCommitted && isTargetToken(w.mToken); } /** diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5c82dba82031..59677f41123a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -327,6 +327,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private SurfaceControl mOverlayLayer; + /** + * A SurfaceControl that contains input overlays used for cases where we need to receive input + * over the entire display. + */ + private SurfaceControl mInputOverlayLayer; + /** A surfaceControl specifically for accessibility overlays. */ private SurfaceControl mA11yOverlayLayer; @@ -1327,6 +1333,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp transaction.reparent(mOverlayLayer, mSurfaceControl); } + if (mInputOverlayLayer == null) { + mInputOverlayLayer = b.setName("Input Overlays").setParent(mSurfaceControl).build(); + } else { + transaction.reparent(mInputOverlayLayer, mSurfaceControl); + } + if (mA11yOverlayLayer == null) { mA11yOverlayLayer = b.setName("Accessibility Overlays").setParent(mSurfaceControl).build(); @@ -1340,7 +1352,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .show(mSurfaceControl) .setLayer(mOverlayLayer, Integer.MAX_VALUE) .show(mOverlayLayer) - .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 1) + .setLayer(mInputOverlayLayer, Integer.MAX_VALUE - 1) + .show(mInputOverlayLayer) + .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 2) .show(mA11yOverlayLayer); } @@ -3351,6 +3365,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // -> this DisplayContent. setRemoteInsetsController(null); mOverlayLayer.release(); + mInputOverlayLayer.release(); mA11yOverlayLayer.release(); mWindowingLayer.release(); mInputMonitor.onDisplayRemoved(); @@ -5704,6 +5719,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mOverlayLayer; } + SurfaceControl getInputOverlayLayer() { + return mInputOverlayLayer; + } + SurfaceControl getA11yOverlayLayer() { return mA11yOverlayLayer; } @@ -7060,6 +7079,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp new Transaction().reparent(sc, getSurfaceControl()) .reparent(mWindowingLayer, null) .reparent(mOverlayLayer, null) + .reparent(mInputOverlayLayer, null) .reparent(mA11yOverlayLayer, null) .apply(); } diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 73fdfe0d1181..8cf471394c63 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -275,11 +275,17 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal + " - DisplayContent not found."); return null; } + final SurfaceControl inputOverlay = dc.getInputOverlayLayer(); + if (inputOverlay == null) { + Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId + + " - Input overlay layer is not initialized."); + return null; + } return mService.makeSurfaceBuilder(dc.getSession()) .setContainerLayer() .setName(name) .setCallsite("createSurfaceForGestureMonitor") - .setParent(dc.getSurfaceControl()) + .setParent(inputOverlay) .build(); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2f0c303ec839..4089a0d7622f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2277,7 +2277,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> int finishTopCrashedActivities(WindowProcessController app, String reason) { Task focusedRootTask = getTopDisplayFocusedRootTask(); final Task[] finishedTask = new Task[1]; - forAllTasks(rootTask -> { + forAllRootTasks(rootTask -> { final Task t = rootTask.finishTopCrashedActivityLocked(app, reason); if (rootTask == focusedRootTask || finishedTask[0] == null) { finishedTask[0] = t; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 43430dd1eed0..f9bbc6810835 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -725,7 +725,7 @@ class Task extends TaskFragment { } catch (RemoteException e) { } } - if (autoRemoveFromRecents(oldParent.asTaskFragment()) || isVoiceSession) { + if (shouldAutoRemoveFromRecents(oldParent.asTaskFragment()) || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. mTaskSupervisor.mRecentTasks.remove(this); @@ -1558,12 +1558,14 @@ class Task extends TaskFragment { return count > 0; } - private boolean autoRemoveFromRecents(TaskFragment oldParentFragment) { + private boolean shouldAutoRemoveFromRecents(TaskFragment oldParentFragment) { // We will automatically remove the task either if it has explicitly asked for // this, or it is empty and has never contained an activity that got shown to - // the user, or it was being embedded in another Task. - return autoRemoveRecents || (!hasChild() && !getHasBeenVisible() - || (oldParentFragment != null && oldParentFragment.isEmbedded())); + // the user, or it was being embedded in another Task, or the display policy + // doesn't allow recents, + return autoRemoveRecents || (!hasChild() && !getHasBeenVisible()) + || (oldParentFragment != null && oldParentFragment.isEmbedded()) + || (mDisplayContent != null && !mDisplayContent.canShowTasksInHostDeviceRecents()); } private void clearPinnedTaskIfNeed() { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 6ede3456d74c..3c85f088ec89 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -1147,6 +1147,7 @@ class TransitionController { Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { t.setEarlyWakeupEnd(); + mAtm.mWindowManager.requestTraversal(); mSnapshotController.setPause(false); mAnimatingState = false; Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 03efb1bf705e..439b7193dd4b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8338,12 +8338,18 @@ public class WindowManagerService extends IWindowManager.Stub + displayId + " - DisplayContent not found."); return null; } - //TODO (b/210039666): Use a method like add/removeDisplayOverlay if available. + final SurfaceControl inputOverlay = dc.getInputOverlayLayer(); + if (inputOverlay == null) { + Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId + + " - Input overlay layer is not initialized."); + return null; + } + // TODO(b/210039666): Use a method like add/removeDisplayOverlay if available. return makeSurfaceBuilder(dc.getSession()) .setContainerLayer() .setName("IME Handwriting Surface") .setCallsite("getHandwritingSurfaceForDisplay") - .setParent(dc.getSurfaceControl()) + .setParent(inputOverlay) .build(); } } diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 7104a80c668d..d833fbd6ffd2 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -459,16 +459,26 @@ <xs:complexType name="autoBrightness"> <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/> <xs:sequence> - <!-- Sets the debounce for autoBrightness brightening in millis--> + <!-- Sets the debounce for autoBrightness brightening in millis --> <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> - <!-- Sets the debounce for autoBrightness darkening in millis--> + <!-- Sets the debounce for autoBrightness darkening in millis --> <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> + <!-- Sets the debounce for autoBrightness brightening in millis while in idle mode --> + <xs:element name="brighteningLightDebounceIdleMillis" type="xs:nonNegativeInteger" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> + <!-- Sets the debounce for autoBrightness darkening in millis while in idle mode --> + <xs:element name="darkeningLightDebounceIdleMillis" type="xs:nonNegativeInteger" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> <!-- Sets the brightness mapping of the desired screen brightness in nits to the corresponding lux for the current display --> <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping" @@ -579,6 +589,10 @@ minOccurs="1" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> + <xs:element type ="xs:string" name="refreshRateThermalThrottlingId"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> <xs:element name="blockingZoneThreshold" type="blockingZoneThreshold" minOccurs="1" maxOccurs="1"> <xs:annotation name="final"/> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 507c9dccda59..d2ac1aae1500 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -3,11 +3,15 @@ package com.android.server.display.config { public class AutoBrightness { ctor public AutoBrightness(); + method public final java.math.BigInteger getBrighteningLightDebounceIdleMillis(); method public final java.math.BigInteger getBrighteningLightDebounceMillis(); + method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis(); method public final java.math.BigInteger getDarkeningLightDebounceMillis(); method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping(); method public boolean getEnabled(); + method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger); method public final void setBrighteningLightDebounceMillis(java.math.BigInteger); + method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger); method public final void setDarkeningLightDebounceMillis(java.math.BigInteger); method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping); method public void setEnabled(boolean); @@ -17,8 +21,10 @@ package com.android.server.display.config { ctor public BlockingZoneConfig(); method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold(); method public final java.math.BigInteger getDefaultRefreshRate(); + method @Nullable public final String getRefreshRateThermalThrottlingId(); method public final void setBlockingZoneThreshold(com.android.server.display.config.BlockingZoneThreshold); method public final void setDefaultRefreshRate(java.math.BigInteger); + method public final void setRefreshRateThermalThrottlingId(@Nullable String); } public class BlockingZoneThreshold { diff --git a/services/permission/Android.bp b/services/permission/Android.bp index dc9b5585cbf2..59ca8512ab8a 100644 --- a/services/permission/Android.bp +++ b/services/permission/Android.bp @@ -17,8 +17,8 @@ filegroup { visibility: ["//frameworks/base/services"], } -java_library_static { - name: "services.permission", +java_library { + name: "services.permission-pre-jarjar", defaults: ["platform_service_defaults"], srcs: [":services.permission-sources"], libs: [ @@ -32,7 +32,6 @@ java_library_static { // Adds reflection-less suppressed exceptions and AutoCloseable.use(). "kotlin-stdlib-jdk7", ], - jarjar_rules: "jarjar-rules.txt", kotlincflags: [ "-Xjvm-default=all", "-Xno-call-assertions", @@ -40,3 +39,9 @@ java_library_static { "-Xno-receiver-assertions", ], } + +java_library { + name: "services.permission", + static_libs: ["services.permission-pre-jarjar"], + jarjar_rules: "jarjar-rules.txt", +} diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 9a19c2df8791..f0705eda5c43 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -985,7 +985,7 @@ class PermissionService( ) { val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy - val appOpName = AppOpsManager.permissionToOp(permissionName) + val appOpName = checkNotNull(AppOpsManager.permissionToOp(permissionName)) val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } } diff --git a/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt index d7d2726c1583..7858b301a6f2 100644 --- a/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt +++ b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt @@ -43,7 +43,7 @@ class ExportedComponentTest { assertThat(packageInstallers).isNotEmpty() packageInstallers.forEach { - val exported = it.receivers.filter { it.exported } + val exported = it.receivers?.filter { it.exported } assertWithMessage("Receivers should not be exported").that(exported).isEmpty() } } diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java index b7a0cf389396..e33ca7775e22 100644 --- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java +++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java @@ -138,14 +138,6 @@ public class CrossUserPackageVisibilityTests { } @Test - public void testGetUserMinAspectRatio_withCrossUserId() { - final int crossUserId = UserHandle.myUserId() + 1; - assertThrows(SecurityException.class, - () -> mIPackageManager.getUserMinAspectRatio( - mInstrumentation.getContext().getPackageName(), crossUserId)); - } - - @Test public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception { final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName()); assertThrows(IllegalArgumentException.class, diff --git a/services/tests/PermissionServiceMockingTests/Android.bp b/services/tests/PermissionServiceMockingTests/Android.bp new file mode 100644 index 000000000000..cedfd2c43e93 --- /dev/null +++ b/services/tests/PermissionServiceMockingTests/Android.bp @@ -0,0 +1,44 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "PermissionServiceMockingTests", + defaults: [ + // This is needed for the ExtendedMockitoRule + "modules-utils-testable-device-config-defaults", + ], + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "mockingservicestests-utils-mockito", + "services.core", + "services.permission-pre-jarjar", + "servicestests-core-utils", + "servicestests-utils", + ], + platform_apis: true, + test_suites: [ + "device-tests", + ], +} diff --git a/services/tests/PermissionServiceMockingTests/AndroidManifest.xml b/services/tests/PermissionServiceMockingTests/AndroidManifest.xml new file mode 100644 index 000000000000..32b64ac1a524 --- /dev/null +++ b/services/tests/PermissionServiceMockingTests/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.server.permission.test"> + + <application android:debuggable="true"> + <uses-library android:name="android.test.mock" android:required="true" /> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.permission.test" + android:label="Permission Service Mocking Tests" /> +</manifest> diff --git a/services/tests/PermissionServiceMockingTests/AndroidTest.xml b/services/tests/PermissionServiceMockingTests/AndroidTest.xml new file mode 100644 index 000000000000..157c4f06fb2a --- /dev/null +++ b/services/tests/PermissionServiceMockingTests/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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="Configuration for PermissionServiceMockingTests"> + <option name="test-tag" value="PermissionServiceMockingTests" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="PermissionServiceMockingTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.server.permission.test" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/services/tests/PermissionServiceMockingTests/OWNERS b/services/tests/PermissionServiceMockingTests/OWNERS new file mode 100644 index 000000000000..dafdf0f8075c --- /dev/null +++ b/services/tests/PermissionServiceMockingTests/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt new file mode 100644 index 000000000000..3ef3a89da94d --- /dev/null +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.permission.test + +import android.content.pm.PackageManager +import android.content.pm.PermissionGroupInfo +import android.content.pm.PermissionInfo +import android.os.Bundle +import android.util.ArrayMap +import android.util.ArraySet +import android.util.SparseArray +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.server.extendedtestutils.wheneverStatic +import com.android.server.permission.access.MutableAccessState +import com.android.server.permission.access.MutableUserState +import com.android.server.permission.access.MutateStateScope +import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.permission.AppIdPermissionPolicy +import com.android.server.permission.access.permission.Permission +import com.android.server.permission.access.permission.PermissionFlags +import com.android.server.pm.parsing.PackageInfoUtils +import com.android.server.pm.pkg.AndroidPackage +import com.android.server.pm.pkg.PackageState +import com.android.server.pm.pkg.PackageUserState +import com.android.server.pm.pkg.component.ParsedPermission +import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.server.testutils.mock +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Mocking unit test for AppIdPermissionPolicy. + */ +@RunWith(AndroidJUnit4::class) +class AppIdPermissionPolicyTest { + private lateinit var oldState: MutableAccessState + private lateinit var newState: MutableAccessState + + private lateinit var androidPackage0: AndroidPackage + private lateinit var androidPackage1: AndroidPackage + + private lateinit var packageState0: PackageState + private lateinit var packageState1: PackageState + + private val appIdPermissionPolicy = AppIdPermissionPolicy() + + @Rule + @JvmField + val extendedMockitoRule = ExtendedMockitoRule.Builder(this) + .spyStatic(PackageInfoUtils::class.java) + .build() + + @Before + fun init() { + oldState = MutableAccessState() + createUserState(USER_ID_0) + createUserState(USER_ID_1) + oldState.mutateExternalState().setPackageStates(ArrayMap()) + + androidPackage0 = mockAndroidPackage( + PACKAGE_NAME_0, + PERMISSION_GROUP_NAME_0, + PERMISSION_NAME_0 + ) + androidPackage1 = mockAndroidPackage( + PACKAGE_NAME_1, + PERMISSION_GROUP_NAME_1, + PERMISSION_NAME_1 + ) + + packageState0 = mockPackageState(APP_ID_0, androidPackage0) + packageState1 = mockPackageState(APP_ID_1, androidPackage1) + } + + private fun mockAndroidPackage( + packageName: String, + permissionGroupName: String, + permissionName: String, + ): AndroidPackage { + val parsedPermissionGroup = mock<ParsedPermissionGroup> { + whenever(name).thenReturn(permissionGroupName) + whenever(metaData).thenReturn(Bundle()) + } + + @Suppress("DEPRECATION") + val permissionGroupInfo = PermissionGroupInfo().apply { + name = permissionGroupName + this.packageName = packageName + } + wheneverStatic { + PackageInfoUtils.generatePermissionGroupInfo( + parsedPermissionGroup, + PackageManager.GET_META_DATA.toLong() + ) + }.thenReturn(permissionGroupInfo) + + val parsedPermission = mock<ParsedPermission> { + whenever(name).thenReturn(permissionName) + whenever(isTree).thenReturn(false) + whenever(metaData).thenReturn(Bundle()) + } + + @Suppress("DEPRECATION") + val permissionInfo = PermissionInfo().apply { + name = permissionName + this.packageName = packageName + } + wheneverStatic { + PackageInfoUtils.generatePermissionInfo( + parsedPermission, + PackageManager.GET_META_DATA.toLong() + ) + }.thenReturn(permissionInfo) + + val requestedPermissions = ArraySet<String>() + return mock { + whenever(this.packageName).thenReturn(packageName) + whenever(this.requestedPermissions).thenReturn(requestedPermissions) + whenever(permissionGroups).thenReturn(listOf(parsedPermissionGroup)) + whenever(permissions).thenReturn(listOf(parsedPermission)) + whenever(signingDetails).thenReturn(mock {}) + } + } + + private fun mockPackageState( + appId: Int, + androidPackage: AndroidPackage, + ): PackageState { + val packageName = androidPackage.packageName + oldState.mutateExternalState().mutateAppIdPackageNames().mutateOrPut(appId) { + MutableIndexedListSet() + }.add(packageName) + + val userStates = SparseArray<PackageUserState>().apply { + put(USER_ID_0, mock { whenever(isInstantApp).thenReturn(false) }) + } + val mockPackageState: PackageState = mock { + whenever(this.packageName).thenReturn(packageName) + whenever(this.appId).thenReturn(appId) + whenever(this.androidPackage).thenReturn(androidPackage) + whenever(isSystem).thenReturn(false) + whenever(this.userStates).thenReturn(userStates) + } + oldState.mutateExternalState().setPackageStates( + oldState.mutateExternalState().packageStates.toMutableMap().apply { + put(packageName, mockPackageState) + } + ) + return mockPackageState + } + + private fun createUserState(userId: Int) { + oldState.mutateUserStatesNoWrite().put(userId, MutableUserState()) + } + + @Test + fun testResetRuntimePermissions_runtimeGranted_getsRevoked() { + val oldFlags = PermissionFlags.RUNTIME_GRANTED + val expectedNewFlags = 0 + testResetRuntimePermissions(oldFlags, expectedNewFlags) {} + } + + @Test + fun testResetRuntimePermissions_roleGranted_getsGranted() { + val oldFlags = PermissionFlags.ROLE + val expectedNewFlags = PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED + testResetRuntimePermissions(oldFlags, expectedNewFlags) {} + } + + @Test + fun testResetRuntimePermissions_nullAndroidPackage_remainsUnchanged() { + val oldFlags = PermissionFlags.RUNTIME_GRANTED + val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED + testResetRuntimePermissions(oldFlags, expectedNewFlags) { + whenever(packageState0.androidPackage).thenReturn(null) + } + } + + private inline fun testResetRuntimePermissions( + oldFlags: Int, + expectedNewFlags: Int, + additionalSetup: () -> Unit + ) { + createSystemStatePermission( + APP_ID_0, + PACKAGE_NAME_0, + PERMISSION_NAME_0, + PermissionInfo.PROTECTION_DANGEROUS + ) + androidPackage0.requestedPermissions.add(PERMISSION_NAME_0) + oldState.mutateUserState(USER_ID_0)!!.mutateAppIdPermissionFlags().mutateOrPut(APP_ID_0) { + MutableIndexedMap() + }.put(PERMISSION_NAME_0, oldFlags) + + additionalSetup() + + mutateState { + with(appIdPermissionPolicy) { + resetRuntimePermissions(PACKAGE_NAME_0, USER_ID_0) + } + } + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) + assertWithMessage( + "After resetting runtime permissions, permission flags did not match" + + " expected values: expectedNewFlags is $expectedNewFlags," + + " actualFlags is $actualFlags, while the oldFlags is $oldFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_permissionsOfMissingSystemApp_getsAdopted() { + testOnPackageAdded { + adoptPermissionTestSetup() + whenever(packageState1.androidPackage).thenReturn(null) + } + + val permission0 = newState.systemState.permissions[PERMISSION_NAME_0] + assertWithMessage( + "After onPackageAdded() is called for a null adopt permission package," + + " the permission package name: ${permission0!!.packageName} did not match" + + " the expected package name: $PACKAGE_NAME_0" + ) + .that(permission0.packageName) + .isEqualTo(PACKAGE_NAME_0) + } + + @Test + fun testOnPackageAdded_permissionsOfExistingSystemApp_notAdopted() { + testOnPackageAdded { + adoptPermissionTestSetup() + } + + val permission0 = newState.systemState.permissions[PERMISSION_NAME_0] + assertWithMessage( + "After onPackageAdded() is called for a non-null adopt permission" + + " package, the permission package name: ${permission0!!.packageName} should" + + " not match the package name: $PACKAGE_NAME_0" + ) + .that(permission0.packageName) + .isNotEqualTo(PACKAGE_NAME_0) + } + + @Test + fun testOnPackageAdded_permissionsOfNonSystemApp_notAdopted() { + testOnPackageAdded { + adoptPermissionTestSetup() + whenever(packageState1.isSystem).thenReturn(false) + } + + val permission0 = newState.systemState.permissions[PERMISSION_NAME_0] + assertWithMessage( + "After onPackageAdded() is called for a non-system adopt permission" + + " package, the permission package name: ${permission0!!.packageName} should" + + " not match the package name: $PACKAGE_NAME_0" + ) + .that(permission0.packageName) + .isNotEqualTo(PACKAGE_NAME_0) + } + + private fun adoptPermissionTestSetup() { + createSystemStatePermission( + APP_ID_1, + PACKAGE_NAME_1, + PERMISSION_NAME_0, + PermissionInfo.PROTECTION_SIGNATURE + ) + whenever(androidPackage0.adoptPermissions).thenReturn(listOf(PACKAGE_NAME_1)) + whenever(packageState1.isSystem).thenReturn(true) + } + + @Test + fun testOnPackageAdded_newPermissionGroup_getsDeclared() { + testOnPackageAdded {} + + assertWithMessage( + "After onPackageAdded() is called when there is no existing" + + " permission groups, the new permission group $PERMISSION_GROUP_NAME_0 is not added" + ) + .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.name) + .isEqualTo(PERMISSION_GROUP_NAME_0) + } + + @Test + fun testOnPackageAdded_systemAppTakingOverPermissionGroupDefinition_getsTakenOver() { + testOnPackageAdded { + whenever(packageState0.isSystem).thenReturn(true) + createSystemStatePermissionGroup(PACKAGE_NAME_1, PERMISSION_GROUP_NAME_0) + } + + assertWithMessage( + "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" + + " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the ownership" + + " of this permission group" + ) + .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.packageName) + .isEqualTo(PACKAGE_NAME_0) + } + + @Test + fun testOnPackageAdded_instantApps_remainsUnchanged() { + testOnPackageAdded { + (packageState0.userStates as SparseArray<PackageUserState>).apply { + put(0, mock { whenever(isInstantApp).thenReturn(true) }) + } + } + + assertWithMessage( + "After onPackageAdded() is called for an instant app," + + " the new permission group $PERMISSION_GROUP_NAME_0 should not be added" + ) + .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]) + .isNull() + } + + @Test + fun testOnPackageAdded_nonSystemAppTakingOverPermissionGroupDefinition_remainsUnchanged() { + testOnPackageAdded { + createSystemStatePermissionGroup(PACKAGE_NAME_1, PERMISSION_GROUP_NAME_0) + } + + assertWithMessage( + "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" + + " exists in the system, non-system app $PACKAGE_NAME_0 shouldn't takeover ownership" + + " of this permission group" + ) + .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.packageName) + .isEqualTo(PACKAGE_NAME_1) + } + + @Test + fun testOnPackageAdded_takingOverPermissionGroupDeclaredBySystemApp_remainsUnchanged() { + testOnPackageAdded { + whenever(packageState1.isSystem).thenReturn(true) + createSystemStatePermissionGroup(PACKAGE_NAME_1, PERMISSION_GROUP_NAME_0) + } + + assertWithMessage( + "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" + + " exists in the system and is owned by a system app, app $PACKAGE_NAME_0 shouldn't" + + " takeover ownership of this permission group" + ) + .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.packageName) + .isEqualTo(PACKAGE_NAME_1) + } + + @Test + fun testOnPackageAdded_newPermission_getsDeclared() { + testOnPackageAdded {} + + assertWithMessage( + "After onPackageAdded() is called when there is no existing" + + " permissions, the new permission $PERMISSION_NAME_0 is not added" + ) + .that(newState.systemState.permissions[PERMISSION_NAME_0]?.name) + .isEqualTo(PERMISSION_NAME_0) + } + + @Test + fun testOnPackageAdded_configPermission_getsTakenOver() { + testOnPackageAdded { + whenever(packageState0.isSystem).thenReturn(true) + createSystemStatePermission( + APP_ID_0, + PACKAGE_NAME_1, + PERMISSION_NAME_0, + PermissionInfo.PROTECTION_DANGEROUS, + Permission.TYPE_CONFIG, + false + ) + } + + assertWithMessage( + "After onPackageAdded() is called for a config permission with" + + " no owner, the ownership is not taken over by a system app $PACKAGE_NAME_0" + ) + .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) + .isEqualTo(PACKAGE_NAME_0) + } + + @Test + fun testOnPackageAdded_systemAppTakingOverPermissionDefinition_getsTakenOver() { + testOnPackageAdded { + whenever(packageState0.isSystem).thenReturn(true) + createSystemStatePermission( + APP_ID_1, + PACKAGE_NAME_1, + PERMISSION_NAME_0, + PermissionInfo.PROTECTION_DANGEROUS + ) + } + + assertWithMessage( + "After onPackageAdded() is called when $PERMISSION_NAME_0 already" + + " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the ownership" + + " of this permission" + ) + .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) + .isEqualTo(PACKAGE_NAME_0) + } + + @Test + fun testOnPackageAdded_nonSystemAppTakingOverPermissionDefinition_remainsUnchanged() { + testOnPackageAdded { + createSystemStatePermission( + APP_ID_1, + PACKAGE_NAME_1, + PERMISSION_NAME_0, + PermissionInfo.PROTECTION_DANGEROUS + ) + } + + assertWithMessage( + "After onPackageAdded() is called when $PERMISSION_NAME_0 already" + + " exists in the system, the non-system app $PACKAGE_NAME_0 shouldn't takeover" + + " ownership of this permission" + ) + .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) + .isEqualTo(PACKAGE_NAME_1) + } + + @Test + fun testOnPackageAdded_takingOverPermissionDeclaredBySystemApp_remainsUnchanged() { + testOnPackageAdded { + whenever(packageState1.isSystem).thenReturn(true) + createSystemStatePermission( + APP_ID_1, + PACKAGE_NAME_1, + PERMISSION_NAME_0, + PermissionInfo.PROTECTION_DANGEROUS + ) + } + + assertWithMessage( + "After onPackageAdded() is called when $PERMISSION_NAME_0 already" + + " exists in system and is owned by a system app, the app $PACKAGE_NAME_0 shouldn't" + + " takeover ownership of this permission" + ) + .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) + .isEqualTo(PACKAGE_NAME_1) + } + + private inline fun testOnPackageAdded(mockBehaviorOverride: () -> Unit) { + mockBehaviorOverride() + + mutateState { + with(appIdPermissionPolicy) { + onPackageAdded(packageState0) + } + } + } + + private inline fun mutateState(action: MutateStateScope.() -> Unit) { + newState = oldState.toMutable() + MutateStateScope(oldState, newState).action() + } + + private fun createSystemStatePermission( + appId: Int, + packageName: String, + permissionName: String, + protectionLevel: Int, + type: Int = Permission.TYPE_MANIFEST, + isReconciled: Boolean = true, + isTree: Boolean = false + ) { + @Suppress("DEPRECATION") + val permissionInfo = PermissionInfo().apply { + name = permissionName + this.packageName = packageName + this.protectionLevel = protectionLevel + } + val permission = Permission(permissionInfo, isReconciled, type, appId) + if (isTree) { + oldState.mutateSystemState().mutatePermissionTrees().put(permissionName, permission) + } else { + oldState.mutateSystemState().mutatePermissions().put(permissionName, permission) + } + } + + private fun createSystemStatePermissionGroup(packageName: String, permissionGroupName: String) { + @Suppress("DEPRECATION") + val permissionGroupInfo = PermissionGroupInfo().apply { + name = permissionGroupName + this.packageName = packageName + } + oldState.mutateSystemState().mutatePermissionGroups()[permissionGroupName] = + permissionGroupInfo + } + + fun getPermissionFlags( + appId: Int, + userId: Int, + permissionName: String, + state: MutableAccessState = newState + ): Int = + state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0) + + companion object { + private const val PACKAGE_NAME_0 = "packageName0" + private const val PACKAGE_NAME_1 = "packageName1" + + private const val APP_ID_0 = 0 + private const val APP_ID_1 = 1 + + private const val PERMISSION_NAME_0 = "permissionName0" + private const val PERMISSION_NAME_1 = "permissionName1" + + private const val PERMISSION_GROUP_NAME_0 = "permissionGroupName0" + private const val PERMISSION_GROUP_NAME_1 = "permissionGroupName1" + + private const val USER_ID_0 = 0 + private const val USER_ID_1 = 1 + } +} diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java index 2d93120681ec..007c0db1b731 100644 --- a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java +++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java @@ -21,12 +21,14 @@ import static com.google.common.truth.Truth8.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; @@ -34,28 +36,35 @@ import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; import android.os.Binder; import android.os.FileUtils; +import android.os.OutcomeReceiver; +import android.os.Process; +import android.security.rkp.service.RegistrationProxy; +import android.security.rkp.service.RemotelyProvisionedKey; +import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.util.Arrays; import java.util.Base64; import java.util.Map; +import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) public class RemoteProvisioningShellCommandTest { - private static class Injector extends RemoteProvisioningShellCommand.Injector { + private Context mContext; - private final Map<String, IRemotelyProvisionedComponent> mIrpcs; + private static class Injector extends RemoteProvisioningShellCommand.Injector { - Injector(Map irpcs) { - mIrpcs = irpcs; - } + Map<String, IRemotelyProvisionedComponent> mIrpcs; + Map<String, RegistrationProxy> mRegistrationProxies; @Override String[] getIrpcNames() { @@ -70,6 +79,12 @@ public class RemoteProvisioningShellCommandTest { } return irpc; } + + @Override + RegistrationProxy getRegistrationProxy( + Context context, int callerUid, String name, Executor executor) { + return mRegistrationProxies.get(name); + } } private static class CommandResult { @@ -111,10 +126,17 @@ public class RemoteProvisioningShellCommandTest { code, FileUtils.readTextFile(out, 0, null), FileUtils.readTextFile(err, 0, null)); } + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + } + @Test public void list_zeroInstances() throws Exception { + Injector injector = new Injector(); + injector.mIrpcs = Map.of(); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of())); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); @@ -124,8 +146,10 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_oneInstances() throws Exception { + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", mock(IRemotelyProvisionedComponent.class)))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); @@ -134,10 +158,12 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_twoInstances() throws Exception { + Injector injector = new Injector(); + injector.mIrpcs = Map.of( + "default", mock(IRemotelyProvisionedComponent.class), + "strongbox", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of( - "default", mock(IRemotelyProvisionedComponent.class), - "strongbox", mock(IRemotelyProvisionedComponent.class)))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); @@ -158,8 +184,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( @@ -189,8 +217,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( @@ -215,8 +245,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x65, 0x6c, 0x6c, 0x6f}); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "default"}); verify(defaultMock).generateCertificateRequestV2(new MacedPublicKey[0], new byte[0]); assertThat(res.getErr()).isEmpty(); @@ -233,8 +265,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x69}); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "--challenge", "dHJpYWw=", "default"}); verify(defaultMock).generateCertificateRequestV2( new MacedPublicKey[0], new byte[] {0x74, 0x72, 0x69, 0x61, 0x6c}); @@ -242,4 +276,82 @@ public class RemoteProvisioningShellCommandTest { assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo("aGk=\n"); } + + @Test + public void certify_sameOrderAsReceived() throws Exception { + String cert1 = "MIIBqDCCAU2gAwIBAgIUI3FFU7xZno/2Xf/wZzKKquP0ov0wCgYIKoZIzj0EAwIw\n" + + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + + "MDgyMjE5MzgxMFoXDTMzMDgxOTE5MzgxMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + + "czOpG6NKOdDjV/yrKjuy0q0jEJvsVLGgTeY+vyKRBJS59OhyRWG6n3aza21bNg5d\n" + + "WE9ruz+bcT0IP4kDbiS0y6NTMFEwHQYDVR0OBBYEFHYfJxCUipNI7qRqvczcWsOb\n" + + "FIDPMB8GA1UdIwQYMBaAFHYfJxCUipNI7qRqvczcWsObFIDPMA8GA1UdEwEB/wQF\n" + + "MAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKm/kpJwlnWkjoLCAddBiSnxbT4EfJIK\n" + + "H0j58tg5VazHAiEAnS/kRzU9AbstOZyD7el/ws3gLXkbUNey3pLFutBWsSU=\n"; + String cert2 = "MIIBpjCCAU2gAwIBAgIUdSzfZzeGr+h70JPO7Sxwdkw99iMwCgYIKoZIzj0EAwIw\n" + + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + + "MDgyMjIwMTcyMFoXDTMzMDgxOTIwMTcyMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + + "voGJi4DxuqH8rzPV6Eq0OVULc0xFzaM0500VBqiQEB7Qt0Ktk2d+3bUrFAb3SZV4\n" + + "6TIdb7SkynvaDtr0x45Ng6NTMFEwHQYDVR0OBBYEFMeGjvGV0ADPBJk5/FPoW9HQ\n" + + "uTc6MB8GA1UdIwQYMBaAFMeGjvGV0ADPBJk5/FPoW9HQuTc6MA8GA1UdEwEB/wQF\n" + + "MAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgd1gu7iiNOQXaQUn5BT3WwWR0Yk78ndWt\n" + + "ew7tRiTOhFcCIFURi6WcNH0oWa6IbwBSMC9aZlo98Fbg+dTwhLAAw+PW\n"; + byte[] cert1Bytes = Base64.getDecoder().decode(cert1.replaceAll("\\s+", "")); + byte[] cert2Bytes = Base64.getDecoder().decode(cert2.replaceAll("\\s+", "")); + byte[] certChain = Arrays.copyOf(cert1Bytes, cert1Bytes.length + cert2Bytes.length); + System.arraycopy(cert2Bytes, 0, certChain, cert1Bytes.length, cert2Bytes.length); + RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); + when(keyMock.getEncodedCertChain()).thenReturn(certChain); + RegistrationProxy defaultMock = mock(RegistrationProxy.class); + doAnswer(invocation -> { + ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) + .onResult(keyMock); + return null; + }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); + + Injector injector = new Injector(); + injector.mRegistrationProxies = Map.of("default", defaultMock); + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + mContext, Process.SHELL_UID, injector); + CommandResult res = exec(cmd, new String[] {"certify", "default"}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(res.getOut()).isEqualTo( + "-----BEGIN CERTIFICATE-----\n" + cert1 + "-----END CERTIFICATE-----\n" + + "-----BEGIN CERTIFICATE-----\n" + cert2 + "-----END CERTIFICATE-----\n"); + } + + @Test + public void certify_noBlankLineBeforeTrailer() throws Exception { + String cert = "MIIB2zCCAYGgAwIBAgIRAOpN7Em1k7gaqLAB2dzXUTYwCgYIKoZIzj0EAwIwKTET\n" + + "MBEGA1UEChMKR29vZ2xlIExMQzESMBAGA1UEAxMJRHJvaWQgQ0EzMB4XDTIzMDgx\n" + + "ODIzMzI1MloXDTIzMDkyMTIzMzI1MlowOTEMMAoGA1UEChMDVEVFMSkwJwYDVQQD\n" + + "EyBlYTRkZWM0OWI1OTNiODFhYThiMDAxZDlkY2Q3NTEzNjBZMBMGByqGSM49AgEG\n" + + "CCqGSM49AwEHA0IABHM/cKZblmlw8bdGbDXnX+ZiLiGjSjaLHXYOoHDrVArAMXUi\n" + + "L6brhcUPaqSGcVLcfFZbaFMOxXW6TsGdQiwJ0iyjejB4MB0GA1UdDgQWBBTYzft+\n" + + "X32TH/Hh+ngwQF6aPhnfXDAfBgNVHSMEGDAWgBQT4JObI9mzNNW2FRsHRcw4zVn2\n" + + "8jAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAVBgorBgEEAdZ5AgEe\n" + + "BAehARoABAAAMAoGCCqGSM49BAMCA0gAMEUCIDc0OR7CzIYw0myTr0y/Brl1nZyk\n" + + "eGSQp615WpTwYhwxAiEApM10gSIKBIo7Z4/FNzkuiz1zZwW9+Dcqisqxkfe6icQ=\n"; + byte[] certBytes = Base64.getDecoder().decode(cert.replaceAll("\\s+", "")); + RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); + when(keyMock.getEncodedCertChain()).thenReturn(certBytes); + RegistrationProxy defaultMock = mock(RegistrationProxy.class); + doAnswer(invocation -> { + ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) + .onResult(keyMock); + return null; + }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); + + Injector injector = new Injector(); + injector.mRegistrationProxies = Map.of("strongbox", defaultMock); + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + mContext, Process.SHELL_UID, injector); + CommandResult res = exec(cmd, new String[] {"certify", "strongbox"}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(res.getOut()).isEqualTo( + "-----BEGIN CERTIFICATE-----\n" + cert + "-----END CERTIFICATE-----\n"); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index a6acd60f3bd7..d16c37a4c07a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -63,8 +63,10 @@ public class AutomaticBrightnessControllerTest { private static final float BRIGHTNESS_MAX_FLOAT = 1.0f; private static final int LIGHT_SENSOR_RATE = 20; private static final int INITIAL_LIGHT_SENSOR_RATE = 20; - private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0; - private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0; + private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 2000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000; + private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000; private static final float DOZE_SCALE_FACTOR = 0.0f; private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; @@ -96,8 +98,9 @@ public class AutomaticBrightnessControllerTest { mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); mContext = InstrumentationRegistry.getContext(); - mController = setupController(mLightSensor, BrightnessMappingStrategy.NO_USER_LUX, - BrightnessMappingStrategy.NO_USER_BRIGHTNESS); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ false, + /* useHorizon= */ true); } @After @@ -109,12 +112,12 @@ public class AutomaticBrightnessControllerTest { } } - private AutomaticBrightnessController setupController(Sensor lightSensor, float userLux, - float userBrightness) { + private void setupController(float userLux, float userBrightness, boolean applyDebounce, + boolean useHorizon) { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); - AutomaticBrightnessController controller = new AutomaticBrightnessController( + mController = new AutomaticBrightnessController( new AutomaticBrightnessController.Injector() { @Override public Handler getBackgroundThreadHandler() { @@ -127,16 +130,19 @@ public class AutomaticBrightnessControllerTest { } }, // pass in test looper instead, pass in offsettable clock - () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor, + () -> { }, mTestLooper.getLooper(), mSensorManager, mLightSensor, mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, - INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, - DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, + INITIAL_LIGHT_SENSOR_RATE, applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, - mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT, - AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness + mIdleBrightnessMappingStrategy, useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1, + useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userBrightness ); when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn( @@ -149,12 +155,10 @@ public class AutomaticBrightnessControllerTest { // Configure the brightness controller and grab an instance of the sensor listener, // through which we can deliver fake (for test) sensor values. - controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, 0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, /* shouldResetShortTermModel= */ true); - - return controller; } @Test @@ -536,8 +540,10 @@ public class AutomaticBrightnessControllerTest { // Now let's do the same for idle mode mController.switchToIdleMode(); // Called once for init, and once when switching, - // setAmbientLux() is called twice and once in updateAutoBrightness() - verify(mBrightnessMappingStrategy, times(5)).isForIdleMode(); + // setAmbientLux() is called twice and once in updateAutoBrightness(), + // nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are + // called twice each. + verify(mBrightnessMappingStrategy, times(9)).isForIdleMode(); // Called when switching. verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout(); verify(mBrightnessMappingStrategy, times(1)).getUserBrightness(); @@ -838,7 +844,158 @@ public class AutomaticBrightnessControllerTest { float userLux = 1000; float userBrightness = 0.3f; - setupController(mLightSensor, userLux, userBrightness); + setupController(userLux, userBrightness, /* applyDebounce= */ true, + /* useHorizon= */ false); verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness); } + + @Test + public void testBrighteningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholds.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // Lux isn't steady yet + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 4500 + // Lux is steady now + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testBrighteningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java index 53d8de0c2bbb..3c3ef9ae166e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java @@ -25,12 +25,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.DisplayManagerInternal; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; -import com.android.server.LocalServices; - -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,25 +46,13 @@ public class ColorFadeTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock); mContext = getInstrumentation().getTargetContext(); } - @After - public void tearDown() { - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - } - @Test public void testPrepareColorFadeForInvalidDisplay() { when(mDisplayManagerInternalMock.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(null); - ColorFade colorFade = new ColorFade(DISPLAY_ID); + ColorFade colorFade = new ColorFade(DISPLAY_ID, mDisplayManagerInternalMock); assertFalse(colorFade.prepare(mContext, ColorFade.MODE_FADE)); } - - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index d9338a9b12bf..82d00a6fcbca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -112,8 +112,6 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(mDisplayDeviceConfig.getBrightness(), BRIGHTNESS, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getNits(), NITS, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA); - assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000); - assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000); assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new @@ -413,6 +411,17 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(new int[]{ -1, 10, 20, 30, 40 }, mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux()); + + // Low/High zone thermal maps + assertEquals(new SurfaceControl.RefreshRateRange(30, 40), + mDisplayDeviceConfig.getLowBlockingZoneThermalMap() + .get(Temperature.THROTTLING_CRITICAL)); + assertEquals(new SurfaceControl.RefreshRateRange(40, 60), + mDisplayDeviceConfig.getHighBlockingZoneThermalMap() + .get(Temperature.THROTTLING_EMERGENCY)); + + // Todo: Add asserts for DensityMapping, + // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. } @Test @@ -540,6 +549,16 @@ public final class DisplayDeviceConfigTest { assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name); } + @Test + public void testLightDebounceFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000); + assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000); + assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounceIdle(), 2500); + assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounceIdle(), 1500); + } + private String getValidLuxThrottling() { return "<luxThrottling>\n" + " <brightnessLimitMap>\n" @@ -632,6 +651,24 @@ public final class DisplayDeviceConfigTest { + " </refreshRateRange>\n" + " </refreshRateThrottlingPoint>\n" + "</refreshRateThrottlingMap>\n" + + "<refreshRateThrottlingMap id=\"thermalLow\">\n" + + " <refreshRateThrottlingPoint>\n" + + " <thermalStatus>critical</thermalStatus>\n" + + " <refreshRateRange>\n" + + " <minimum>30</minimum>\n" + + " <maximum>40</maximum>\n" + + " </refreshRateRange>\n" + + " </refreshRateThrottlingPoint>\n" + + "</refreshRateThrottlingMap>\n" + + "<refreshRateThrottlingMap id=\"thermalHigh\">\n" + + " <refreshRateThrottlingPoint>\n" + + " <thermalStatus>emergency</thermalStatus>\n" + + " <refreshRateRange>\n" + + " <minimum>40</minimum>\n" + + " <maximum>60</maximum>\n" + + " </refreshRateRange>\n" + + " </refreshRateThrottlingPoint>\n" + + "</refreshRateThrottlingMap>\n" + "<refreshRateThrottlingMap id=\"test\">\n" + " <refreshRateThrottlingPoint>\n" + " <thermalStatus>emergency</thermalStatus>\n" @@ -704,6 +741,12 @@ public final class DisplayDeviceConfigTest { + "<autoBrightness>\n" + "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n" + "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n" + + "<brighteningLightDebounceIdleMillis>" + + "2500" + + "</brighteningLightDebounceIdleMillis>\n" + + "<darkeningLightDebounceIdleMillis>" + + "1500" + + "</darkeningLightDebounceIdleMillis>\n" + "<displayBrightnessMapping>\n" + "<displayBrightnessPoint>\n" + "<lux>50</lux>\n" @@ -969,6 +1012,8 @@ public final class DisplayDeviceConfigTest { + "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n" + "<lowerBlockingZoneConfigs>\n" + "<defaultRefreshRate>75</defaultRefreshRate>\n" + + "<refreshRateThermalThrottlingId>thermalLow" + + "</refreshRateThermalThrottlingId>\n" + "<blockingZoneThreshold>\n" + "<displayBrightnessPoint>\n" + "<lux>50</lux>\n" @@ -990,6 +1035,8 @@ public final class DisplayDeviceConfigTest { + "</lowerBlockingZoneConfigs>\n" + "<higherBlockingZoneConfigs>\n" + "<defaultRefreshRate>90</defaultRefreshRate>\n" + + "<refreshRateThermalThrottlingId>thermalHigh" + + "</refreshRateThermalThrottlingId>\n" + "<blockingZoneThreshold>\n" + "<displayBrightnessPoint>\n" + "<lux>70</lux>\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 4c3d80a6422b..4cbdd09319cc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -1061,6 +1061,8 @@ public final class DisplayPowerController2Test { anyInt(), anyLong(), anyLong(), + anyLong(), + anyLong(), anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1350,6 +1352,7 @@ public final class DisplayPowerController2Test { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 01e49f2e207d..68bbcbc1df51 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -63,8 +63,8 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.modules.utils.testing.ExtendedMockitoRule; -import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; @@ -74,7 +74,6 @@ import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.policy.WindowManagerPolicy; import com.android.server.testutils.OffsettableClock; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -140,6 +139,9 @@ public final class DisplayPowerControllerTest { .spyStatic(BatteryStatsService.class) .build(); + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Before public void setUp() throws Exception { mClock = new OffsettableClock.Stopped(); @@ -153,9 +155,10 @@ public final class DisplayPowerControllerTest { Settings.System.putFloatForUser(mContext.getContentResolver(), Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT); - addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock); - addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class, - mCdsiMock); + mLocalServiceKeeperRule.overrideLocalService( + WindowManagerPolicy.class, mWindowManagerPolicyMock); + mLocalServiceKeeperRule.overrideLocalService( + ColorDisplayService.ColorDisplayServiceInternal.class, mCdsiMock); mContext.addMockSystemService(PowerManager.class, mPowerManagerMock); @@ -167,12 +170,6 @@ public final class DisplayPowerControllerTest { mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); } - @After - public void tearDown() { - LocalServices.removeServiceForTest(WindowManagerPolicy.class); - LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); - } - @Test public void testReleaseProxSuspendBlockersOnExit() throws Exception { when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); @@ -1071,6 +1068,8 @@ public final class DisplayPowerControllerTest { anyInt(), anyLong(), anyLong(), + anyLong(), + anyLong(), anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1124,14 +1123,6 @@ public final class DisplayPowerControllerTest { verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true); } - /** - * Creates a mock and registers it to {@link LocalServices}. - */ - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); @@ -1330,6 +1321,7 @@ public final class DisplayPowerControllerTest { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index b088dbfcf13b..168eefcb4f0c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -39,6 +39,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -1093,6 +1094,196 @@ public class DisplayModeDirectorTest { } @Test + public void testLockFpsForHighZoneWithThermalCondition() throws Exception { + // First, configure brightness zones or DMD won't register for sensor data. + final FakeDeviceConfig config = mInjector.getDeviceConfig(); + config.setHighDisplayBrightnessThresholds(new int[] { 200 }); + config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); + + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0); + setPeakRefreshRate(120 /*fps*/); + director.getSettingsObserver().setDefaultRefreshRate(120); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + // Set the thresholds for High Zone + DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class); + when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 }); + when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 }); + when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] {}); + when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] {}); + + // Set the thermal condition for refresh rate range + when(ddcMock.getHighBlockingZoneThermalMap()).thenReturn( + new SparseArray<RefreshRateRange>() {{ + put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60)); + }} + ); + director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + // Get the display listener so that we can send it new brightness events + ArgumentCaptor<DisplayListener> displayListenerCaptor = + ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + any(Handler.class), + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + DisplayListener displayListener = displayListenerCaptor.getValue(); + + // Get the sensor listener so that we can give it new light sensor events + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) + .registerListener( + listenerCaptor.capture(), + eq(lightSensor), + anyInt(), + any(Handler.class)); + SensorEventListener sensorListener = listenerCaptor.getValue(); + + // Get the thermal listener so that we can give it new thermal conditions + ArgumentCaptor<IThermalEventListener> thermalListenerCaptor = + ArgumentCaptor.forClass(IThermalEventListener.class); + verify(mInjector, atLeastOnce()).registerThermalServiceListener( + thermalListenerCaptor.capture()); + List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues(); + + setBrightness(100, 100, displayListener); + // Sensor reads 2000 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000)); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertThat(vote).isNull(); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNull(); + + // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this + // parameter to the necessary threshold + setBrightness(255, 255, displayListener); + // Sensor reads 9000 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000)); + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + + // Set critical and check new refresh rate + Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL); + for (var listener : thermalListeners) { + listener.notifyThrottling(temp); + } + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + } + + @Test + public void testLockFpsForLowZoneWithThermalCondition() throws Exception { + // First, configure brightness zones or DMD won't register for sensor data. + final FakeDeviceConfig config = mInjector.getDeviceConfig(); + config.setHighDisplayBrightnessThresholds(new int[] { 200 }); + config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); + + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0); + setPeakRefreshRate(120 /*fps*/); + director.getSettingsObserver().setDefaultRefreshRate(120); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + // Set the thresholds for Low Zone + DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class); + when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 }); + when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 }); + when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] { 10 }); + when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] { 10 }); + + // Set the thermal condition for refresh rate range + when(ddcMock.getLowBlockingZoneThermalMap()).thenReturn( + new SparseArray<RefreshRateRange>() {{ + put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60)); + }} + ); + director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + // Get the display listener so that we can send it new brightness events + ArgumentCaptor<DisplayListener> displayListenerCaptor = + ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + any(Handler.class), + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + DisplayListener displayListener = displayListenerCaptor.getValue(); + + // Get the sensor listener so that we can give it new light sensor events + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) + .registerListener( + listenerCaptor.capture(), + eq(lightSensor), + anyInt(), + any(Handler.class)); + SensorEventListener sensorListener = listenerCaptor.getValue(); + + // Get the thermal listener so that we can give it new thermal conditions + ArgumentCaptor<IThermalEventListener> thermalListenerCaptor = + ArgumentCaptor.forClass(IThermalEventListener.class); + verify(mInjector, atLeastOnce()).registerThermalServiceListener( + thermalListenerCaptor.capture()); + List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues(); + + setBrightness(100, 100, displayListener); + // Sensor reads 2000 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000)); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertThat(vote).isNull(); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNull(); + + // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this + // parameter to the necessary threshold + setBrightness(5, 5, displayListener); + // Sensor reads 9 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9)); + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + + // Set critical and check new refresh rate + Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL); + for (var listener : thermalListeners) { + listener.notifyThrottling(temp); + } + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + } + + @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. final FakeDeviceConfig config = mInjector.getDeviceConfig(); @@ -2907,6 +3098,10 @@ public class DisplayModeDirectorTest { } @Override + public void unregisterThermalServiceListener(IThermalEventListener listener) { + } + + @Override public boolean supportsFrameRateOverride() { return true; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java index 183a84de0eb0..f9fbf1bd5085 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java @@ -38,7 +38,7 @@ import android.util.TypedValue; import androidx.test.InstrumentationRegistry; import com.android.internal.R; -import com.android.server.LocalServices; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.server.display.TestUtils; import com.android.server.display.color.ColorDisplayService; import com.android.server.display.utils.AmbientFilter; @@ -47,6 +47,7 @@ import com.android.server.display.utils.AmbientFilterStubber; import com.google.common.collect.ImmutableList; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -90,6 +91,9 @@ public final class AmbientLuxTest { @Mock private TypedArray mStrongDisplayColorTemperatures; @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock; + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -156,8 +160,9 @@ public final class AmbientLuxTest { R.array.config_displayWhiteBalanceHighLightAmbientBiasesStrong)) .thenReturn(mHighLightBiasesStrong); mockThrottler(); - LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); - LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class, + + mLocalServiceKeeperRule.overrideLocalService( + ColorDisplayService.ColorDisplayServiceInternal.class, mColorDisplayServiceInternalMock); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt index cffd027a58a3..6c392751abe6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -131,7 +131,7 @@ class SharedLibrariesImplTest { wheneverStatic { HexEncoding.decode(STATIC_LIB_NAME, false) } .thenReturn(PackageUtils.computeSha256DigestBytes( mSettings.getPackageLPr(STATIC_LIB_PACKAGE_NAME) - .pkg.signingDetails.signatures!![0].toByteArray())) + .pkg!!.signingDetails.signatures!![0].toByteArray())) } @Test @@ -239,7 +239,7 @@ class SharedLibrariesImplTest { testPackageSetting.setPkgStateLibraryFiles(listOf()) assertThat(testPackageSetting.usesLibraryFiles).isEmpty() - mSharedLibrariesImpl.updateSharedLibraries(testPackageSetting.pkg, testPackageSetting, + mSharedLibrariesImpl.updateSharedLibraries(testPackageSetting.pkg!!, testPackageSetting, null /* changingLib */, null /* changingLibSetting */, mExistingPackages) assertThat(testPackageSetting.usesLibraryFiles).hasSize(1) @@ -252,7 +252,7 @@ class SharedLibrariesImplTest { testPackageSetting.setPkgStateLibraryFiles(listOf()) assertThat(testPackageSetting.usesLibraryFiles).isEmpty() - mSharedLibrariesImpl.updateSharedLibraries(testPackageSetting.pkg, testPackageSetting, + mSharedLibrariesImpl.updateSharedLibraries(testPackageSetting.pkg!!, testPackageSetting, null /* changingLib */, null /* changingLibSetting */, mExistingPackages) assertThat(testPackageSetting.usesLibraryFiles).hasSize(2) @@ -266,7 +266,7 @@ class SharedLibrariesImplTest { testPackageSetting.setPkgStateLibraryFiles(listOf()) assertThat(testPackageSetting.usesLibraryFiles).isEmpty() - mSharedLibrariesImpl.updateSharedLibraries(testPackageSetting.pkg, testPackageSetting, + mSharedLibrariesImpl.updateSharedLibraries(testPackageSetting.pkg!!, testPackageSetting, null /* changingLib */, null /* changingLibSetting */, mExistingPackages) assertThat(testPackageSetting.usesLibraryFiles).hasSize(3) diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 9fe743da225e..b79c7bee90b1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; @@ -38,6 +39,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -170,6 +172,8 @@ public class FullScreenMagnificationGestureHandlerTest { AccessibilityTraceManager mMockTraceManager; @Mock FullScreenMagnificationVibrationHelper mMockFullScreenMagnificationVibrationHelper; + @Mock + FullScreenMagnificationGestureHandler.MagnificationLogger mMockMagnificationLogger; @Rule public final TestableContext mContext = new TestableContext(getInstrumentation().getContext()); @@ -253,7 +257,7 @@ public class FullScreenMagnificationGestureHandlerTest { mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback, detectTripleTap, detectShortcutTrigger, mWindowMagnificationPromptController, DISPLAY_0, - mMockFullScreenMagnificationVibrationHelper); + mMockFullScreenMagnificationVibrationHelper, mMockMagnificationLogger); if (isWatch()) { h.setSinglePanningEnabled(true); } else { @@ -428,6 +432,70 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void testLongTapAfterShortcutTriggered_neverLogMagnificationTripleTap() { + goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); + + longTap(); + + verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); + } + + @Test + public void testSwipeAndHoldAfterShortcutTriggered_neverLogMagnificationTripleTap() { + goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); + + swipeAndHold(); + + verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); + } + + @Test + public void testTripleTap_isNotActivated_logMagnificationTripleTapIsEnabled() { + goFromStateIdleTo(STATE_IDLE); + + tap(); + tap(); + longTap(); + + verify(mMockMagnificationLogger).logMagnificationTripleTap(true); + } + + @Test + public void testTripleTap_isActivated_logMagnificationTripleTapIsNotEnabled() { + goFromStateIdleTo(STATE_ACTIVATED); + reset(mMockMagnificationLogger); + + tap(); + tap(); + longTap(); + + verify(mMockMagnificationLogger).logMagnificationTripleTap(false); + } + + @Test + public void testTripleTapAndHold_isNotActivated_logMagnificationTripleTapIsEnabled() { + goFromStateIdleTo(STATE_IDLE); + + tap(); + tap(); + swipeAndHold(); + + verify(mMockMagnificationLogger).logMagnificationTripleTap(true); + } + + @Test + public void testTripleTapAndHold_isActivated_logMagnificationTripleTapIsNotEnabled() { + goFromStateIdleTo(STATE_ACTIVATED); + reset(mMockMagnificationLogger); + + tap(); + tap(); + swipeAndHold(); + + verify(mMockMagnificationLogger).logMagnificationTripleTap(false); + } + + @Test public void testTripleTapAndHold_zoomsImmediately() { assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS, STATE_NON_ACTIVATED_ZOOMED_TMP); assertZoomsImmediatelyOnSwipeFrom(STATE_ACTIVATED_2TAPS, STATE_ZOOMED_FURTHER_TMP); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java index 746fb53c7254..64e776e35f00 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics; +import static com.android.server.biometrics.AuthenticationStatsCollector.MAXIMUM_ENROLLMENT_NOTIFICATIONS; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -44,9 +46,11 @@ import com.android.internal.R; import com.android.server.biometrics.sensors.BiometricNotification; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.io.File; @@ -54,6 +58,9 @@ import java.io.File; @SmallTest public class AuthenticationStatsCollectorTest { + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + private AuthenticationStatsCollector mAuthenticationStatsCollector; private static final float FRR_THRESHOLD = 0.2f; private static final int USER_ID_1 = 1; @@ -75,7 +82,6 @@ public class AuthenticationStatsCollectorTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); when(mContext.getResources()).thenReturn(mResources); when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold), @@ -130,6 +136,33 @@ public class AuthenticationStatsCollectorTest { assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0); } + /** + * Our current use case does not need the counters to update after the notification + * limit is reached. If we need these counters to continue counting in the future, + * a separate privacy review must be done. + */ + @Test + public void authenticate_notificationExceeded_mapMustNotBeUpdated() { + + mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1, + new AuthenticationStats(USER_ID_1, 400 /* totalAttempts */, + 40 /* rejectedAttempts */, + MAXIMUM_ENROLLMENT_NOTIFICATIONS /* enrollmentNotifications */, + 0 /* modality */)); + + mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */); + + AuthenticationStats authenticationStats = + mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1); + + assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1); + // Assert that counters haven't been updated. + assertThat(authenticationStats.getTotalAttempts()).isEqualTo(400); + assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(40); + assertThat(authenticationStats.getEnrollmentNotifications()) + .isEqualTo(MAXIMUM_ENROLLMENT_NOTIFICATIONS); + } + @Test public void authenticate_frrNotExceeded_notificationNotExceeded_shouldNotSendNotification() { @@ -156,7 +189,8 @@ public class AuthenticationStatsCollectorTest { mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1, new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */, - 400 /* rejectedAttempts */, 2 /* enrollmentNotifications */, + 400 /* rejectedAttempts */, + MAXIMUM_ENROLLMENT_NOTIFICATIONS /* enrollmentNotifications */, 0 /* modality */)); mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */); @@ -164,12 +198,12 @@ public class AuthenticationStatsCollectorTest { // Assert that no notification should be sent. verify(mBiometricNotification, never()).sendFaceEnrollNotification(any()); verify(mBiometricNotification, never()).sendFpEnrollNotification(any()); - // Assert that data has been reset. + // Assert that data hasn't been reset. AuthenticationStats authenticationStats = mAuthenticationStatsCollector .getAuthenticationStatsForUser(USER_ID_1); - assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0); - assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0); - assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f); + assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500); + assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400); + assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 908afc861f8b..00e35ecece8b 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -226,6 +226,7 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceManagerService mVdms; private VirtualDeviceManagerInternal mLocalService; private VirtualDeviceManagerService.VirtualDeviceManagerImpl mVdm; + private VirtualDeviceLog mVirtualDeviceLog; @Mock private InputController.NativeWrapper mNativeWrapperMock; @Mock @@ -370,6 +371,7 @@ public class VirtualDeviceManagerServiceTest { mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); mVdm = mVdms.new VirtualDeviceManagerImpl(); + mVirtualDeviceLog = new VirtualDeviceLog(mContext); mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1); mSensorController = mDeviceImpl.getSensorControllerForTest(); } @@ -1730,7 +1732,7 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid, VirtualDeviceParams params) { VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, mVdms, new Binder(), + mAssociationInfo, mVdms, mVirtualDeviceLog, new Binder(), new AttributionSource(ownerUid, "com.android.virtualdevice.test", "virtualdevice"), virtualDeviceId, mInputController, mCameraAccessController, diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index ee3ab9744fdd..9fca513e50b9 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -149,32 +149,6 @@ public class HintManagerServiceTest { } @Test - public void testCreateHintSession_exceedsLimit() throws Exception { - HintManagerService service = createService(); - IBinder token1 = new Binder(); - IBinder token2 = new Binder(); - - for (int i = 0; i < 10; i++) { - IHintSession a = service.getBinderServiceInstance().createHintSession(token1, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION); - assertNotNull(a); - } - - for (int i = 0; i < 10; i++) { - IHintSession b = service.getBinderServiceInstance().createHintSession(token2, - SESSION_TIDS_B, DEFAULT_TARGET_DURATION); - assertNotNull(b); - } - - assertThrows(IllegalStateException.class, - () -> service.getBinderServiceInstance().createHintSession(token1, SESSION_TIDS_A, - DEFAULT_TARGET_DURATION)); - assertThrows(IllegalStateException.class, - () -> service.getBinderServiceInstance().createHintSession(token2, SESSION_TIDS_B, - DEFAULT_TARGET_DURATION)); - } - - @Test public void testPauseResumeHintSession() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); 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 579bbc8a417a..37f49838a61b 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6045,31 +6045,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testVisitUris_styleExtrasWithoutStyle() { - Notification notification = new Notification.Builder(mContext, "a") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .build(); + Notification.Builder notification = new Notification.Builder(mContext, "a") + .setSmallIcon(android.R.drawable.sym_def_app_icon); - Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle( - personWithIcon("content://user")) - .addHistoricMessage(new Notification.MessagingStyle.Message("Heyhey!", - System.currentTimeMillis(), - personWithIcon("content://historicalMessenger"))) - .addMessage(new Notification.MessagingStyle.Message("Are you there", - System.currentTimeMillis(), - personWithIcon("content://messenger"))) - .setShortcutIcon( - Icon.createWithContentUri("content://conversationShortcut")); - messagingStyle.addExtras(notification.extras); // Instead of Builder.setStyle(style). - - Notification.CallStyle callStyle = Notification.CallStyle.forOngoingCall( - personWithIcon("content://caller"), - PendingIntent.getActivity(mContext, 0, new Intent(), - PendingIntent.FLAG_IMMUTABLE)) - .setVerificationIcon(Icon.createWithContentUri("content://callVerification")); - callStyle.addExtras(notification.extras); // Same. + Bundle messagingExtras = new Bundle(); + messagingExtras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, + personWithIcon("content://user")); + messagingExtras.putParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES, + new Bundle[] { new Notification.MessagingStyle.Message("Heyhey!", + System.currentTimeMillis() - 100, + personWithIcon("content://historicalMessenger")).toBundle()}); + messagingExtras.putParcelableArray(Notification.EXTRA_MESSAGES, + new Bundle[] { new Notification.MessagingStyle.Message("Are you there?", + System.currentTimeMillis(), + personWithIcon("content://messenger")).toBundle()}); + messagingExtras.putParcelable(Notification.EXTRA_CONVERSATION_ICON, + Icon.createWithContentUri("content://conversationShortcut")); + notification.addExtras(messagingExtras); + + Bundle callExtras = new Bundle(); + callExtras.putParcelable(Notification.EXTRA_CALL_PERSON, + personWithIcon("content://caller")); + callExtras.putParcelable(Notification.EXTRA_VERIFICATION_ICON, + Icon.createWithContentUri("content://callVerification")); + notification.addExtras(callExtras); Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - notification.visitUris(visitor); + notification.build().visitUris(visitor); verify(visitor).accept(eq(Uri.parse("content://user"))); verify(visitor).accept(eq(Uri.parse("content://historicalMessenger"))); 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 81d939f1f05f..9b745f5aaf4f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -115,6 +115,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Parcel; +import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; @@ -209,6 +210,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Mock Context mContext; @Mock ZenModeHelper mMockZenModeHelper; @Mock AppOpsManager mAppOpsManager; + @Mock ManagedServices.UserProfiles mUserProfiles; @Mock PermissionManager mPermissionManager; private NotificationManager.Policy mTestNotificationPolicy; @@ -327,10 +329,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) .thenReturn(appPermissions); + when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0})); + mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, mStatsEventBuilderFactory, false); resetZenModeHelper(); @@ -678,7 +682,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migrates() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"2\">\n" + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 @@ -743,9 +748,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getPackageUidAsUser("pkg3", USER_SYSTEM)).thenReturn(UNKNOWN_UID); @@ -822,7 +824,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -879,7 +882,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_permissionNotificationOff() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ false); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -936,7 +940,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"4\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -991,9 +996,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migration_NoUid() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"2\">\n" + "<package name=\"something\" show_badge=\"true\">\n" @@ -1024,9 +1026,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_NoUid() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"3\">\n" + "<package name=\"something\" show_badge=\"true\">\n" @@ -1056,9 +1055,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForNonBackup_postMigration() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); @@ -1142,9 +1138,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); @@ -1234,9 +1227,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noExternal() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false)); appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false)); @@ -1319,9 +1309,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); @@ -1533,7 +1520,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { new FileNotFoundException("")).thenReturn(resId); mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, false); NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); @@ -2477,9 +2465,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); // create notification channel that can bypass dnd, but app is blocked @@ -2508,9 +2493,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); // create notification channel that can bypass dnd, but app is blocked @@ -2533,9 +2515,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); // create notification channel that can bypass dnd, but app is blocked @@ -2588,9 +2567,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); @@ -2602,15 +2578,58 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start notification policy off with mAreChannelsBypassingDnd = false mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); resetZenModeHelper(); } @Test + public void syncChannelsBypassingDnd_includesProfilesOfCurrentUser() throws Exception { + when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0, 10})); + when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + when(mPm.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenReturn(appInfo); + + NotificationChannel withBypass = new NotificationChannel("1", "with", IMPORTANCE_DEFAULT); + withBypass.setBypassDnd(true); + NotificationChannel withoutBypass = new NotificationChannel("2", "without", + IMPORTANCE_DEFAULT); + withoutBypass.setBypassDnd(false); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(0, 444), withoutBypass, + false, false, Process.SYSTEM_UID, true); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass, + false, false, Process.SYSTEM_UID, true); + + mHelper.syncChannelsBypassingDnd(); + + assertThat(mHelper.areChannelsBypassingDnd()).isTrue(); + } + + @Test + public void syncChannelsBypassingDnd_excludesOtherUsers() throws Exception { + when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0})); + when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + when(mPm.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenReturn(appInfo); + + NotificationChannel withBypass = new NotificationChannel("1", "with", IMPORTANCE_DEFAULT); + withBypass.setBypassDnd(true); + NotificationChannel withoutBypass = new NotificationChannel("2", "without", + IMPORTANCE_DEFAULT); + withoutBypass.setBypassDnd(false); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(0, 444), withoutBypass, + false, false, Process.SYSTEM_UID, true); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass, + false, false, Process.SYSTEM_UID, true); + + mHelper.syncChannelsBypassingDnd(); + + assertThat(mHelper.areChannelsBypassingDnd()).isFalse(); + } + + @Test public void testCreateDeletedChannel() throws Exception { long[] vibration = new long[]{100, 67, 145, 156}; NotificationChannel channel = @@ -3705,9 +3724,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM); assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3719,9 +3736,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setHideSilentStatusIcons(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3789,9 +3803,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.canShowBadge(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3802,9 +3813,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3816,9 +3824,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.revokeNotificationDelegate(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3832,9 +3837,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3888,9 +3890,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getAppLockedFields(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_SELECTED, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3926,9 +3925,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getAppLockedFields(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); @@ -4584,10 +4580,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testPlaceholderConversationId_shortcutRequired() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"foo:placeholder_id\"/>" @@ -4604,10 +4596,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testNormalConversationId_shortcutRequired() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"other\"/>" @@ -4624,10 +4612,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testNoConversationId_shortcutRequired() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\"/>" @@ -4644,10 +4628,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_noTime() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\" deleted=\"true\"/>" @@ -4664,13 +4644,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_twice() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, UID_P, false); + assertTrue(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id", UID_P, false)); assertFalse(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id", @@ -4679,10 +4656,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_recentTime() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, UID_P, false); @@ -4698,9 +4671,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())), null); parser.nextTag(); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.readXml(parser, true, USER_SYSTEM); NotificationChannel nc = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true); @@ -4710,10 +4680,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testUnDelete_time() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, UID_P, false); @@ -4732,10 +4698,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_longTime() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30); final String xml = "<ranking version=\"1\">\n" diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java index 6f6540627f67..05a1482b9be6 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java @@ -112,7 +112,7 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase { // Show assistant. mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_ASSISTANT); sendKey(KEYCODE_POWER, true); - mPhoneWindowManager.assertAssistLaunch(); + mPhoneWindowManager.assertSearchManagerLaunchAssist(); // Show global actions. mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_GLOBAL_ACTIONS); diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java index c433e644a6a0..eab8757b7331 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -16,11 +16,15 @@ package com.android.server.policy; +import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; +import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY; +import android.content.ComponentName; import android.provider.Settings; import org.junit.Test; @@ -32,6 +36,9 @@ import org.junit.Test; * atest WmTests:StemKeyGestureTests */ public class StemKeyGestureTests extends ShortcutKeyTestBase { + + private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity"; + /** * Stem single key should not launch behavior during set up. */ @@ -63,6 +70,57 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { mPhoneWindowManager.assertOpenAllAppView(); } + /** + * Stem single key should not launch behavior during set up. + */ + @Test + public void stemSingleKey_launchTargetActivity() { + overrideBehavior( + STEM_PRIMARY_BUTTON_SHORT_PRESS, + SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideStartActivity(); + mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + mPhoneWindowManager.assumeResolveActivityNotNull(); + + ComponentName targetComponent = ComponentName.unflattenFromString(TEST_TARGET_ACTIVITY); + mPhoneWindowManager.overrideStemPressTargetActivity(targetComponent); + + sendKey(KEYCODE_STEM_PRIMARY); + + mPhoneWindowManager.assertActivityTargetLaunched(targetComponent); + } + + @Test + public void stemLongKey_triggerSearchServiceToLaunchAssist() { + overrideBehavior( + STEM_PRIMARY_BUTTON_LONG_PRESS, + LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.setupAssistForLaunch(); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + + sendKey(KEYCODE_STEM_PRIMARY, /* longPress= */ true); + mPhoneWindowManager.assertSearchManagerLaunchAssist(); + } + + @Test + public void stemLongKey_whenNoSearchService_triggerStatusBarToStartAssist() { + overrideBehavior( + STEM_PRIMARY_BUTTON_LONG_PRESS, + LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.setupAssistForLaunch(); + mPhoneWindowManager.overrideSearchManager(null); + mPhoneWindowManager.overrideStatusBarManagerInternal(); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + + sendKey(KEYCODE_STEM_PRIMARY, /* longPress= */ true); + mPhoneWindowManager.assertStatusBarStartAssist(); + } + + private void overrideBehavior(String key, int expectedBehavior) { Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior); } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index ef3a6edf9759..bc8f06a48ffb 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -59,9 +59,11 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.SearchManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.hardware.SensorPrivacyManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; @@ -230,6 +232,7 @@ class TestPhoneWindowManager { doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mSensorPrivacyManager).when(mContext).getSystemService( eq(SensorPrivacyManager.class)); + doReturn(mSearchManager).when(mContext).getSystemService(eq(SearchManager.class)); doReturn(false).when(mPackageManager).hasSystemFeature(any()); try { doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager) @@ -355,11 +358,7 @@ class TestPhoneWindowManager { case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: break; case LONG_PRESS_POWER_ASSISTANT: - doNothing().when(mPhoneWindowManager).sendCloseSystemWindows(); - doReturn(true).when(mPhoneWindowManager).isUserSetupComplete(); - doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); - doReturn(mSearchManager).when(mContext) - .getSystemService(eq(Context.SEARCH_SERVICE)); + setupAssistForLaunch(); mPhoneWindowManager.mLongPressOnPowerAssistantTimeoutMs = 500; break; } @@ -433,6 +432,22 @@ class TestPhoneWindowManager { doReturn(isShowing).when(mKeyguardServiceDelegate).isShowing(); } + void setupAssistForLaunch() { + doNothing().when(mPhoneWindowManager).sendCloseSystemWindows(); + doReturn(true).when(mPhoneWindowManager).isUserSetupComplete(); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); + } + + void overrideSearchManager(SearchManager searchManager) { + mPhoneWindowManager.mSearchManager = searchManager; + } + + void assumeResolveActivityNotNull() { + ResolveInfo resolveInfo = new ResolveInfo(); + doReturn(resolveInfo).when(mPackageManager).resolveActivity(any(), anyInt()); + doReturn(mPackageManager).when(mContext).getPackageManager(); + } + void overrideKeyEventSource(int vendorId, int productId) { InputDevice device = new InputDevice.Builder().setId(1).setVendorId(vendorId).setProductId( productId).setSources(InputDevice.SOURCE_KEYBOARD).setKeyboardType( @@ -458,6 +473,10 @@ class TestPhoneWindowManager { doReturn(true).when(mPhoneWindowManager).isUserSetupComplete(); } + void overrideStemPressTargetActivity(ComponentName component) { + mPhoneWindowManager.mPrimaryShortPressTargetActivity = component; + } + /** * Below functions will check the policy behavior could be invoked. */ @@ -517,7 +536,7 @@ class TestPhoneWindowManager { .interceptPowerKeyDown(any(), anyBoolean(), any()); } - void assertAssistLaunch() { + void assertSearchManagerLaunchAssist() { waitForIdle(); verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any()); } @@ -540,6 +559,11 @@ class TestPhoneWindowManager { verify(mStatusBarManagerInternal).showRecentApps(anyBoolean()); } + void assertStatusBarStartAssist() { + waitForIdle(); + verify(mStatusBarManagerInternal).startAssist(any()); + } + void assertSwitchKeyboardLayout(int direction) { waitForIdle(); if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { @@ -613,6 +637,14 @@ class TestPhoneWindowManager { .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class)); } + void assertActivityTargetLaunched(ComponentName targetActivity) { + waitForIdle(); + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) + .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); + Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent()); + } + void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent, int expectedKey, int expectedModifierState, String errorMsg) { waitForIdle(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 77d5908c63a5..25619b9ec5e8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1030,17 +1030,21 @@ public class RecentTasksTest extends WindowTestsBase { // If the task has a non-stopped activity, the removal will wait for its onDestroy. final Task task = tasks.get(0); + final ActivityRecord bottom = new ActivityBuilder(mAtm).setTask(task).build(); final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).build(); - top.lastVisibleTime = 123; + bottom.lastVisibleTime = top.lastVisibleTime = 123; top.setState(ActivityRecord.State.RESUMED, "test"); mRecentTasks.removeTasksByPackageName(task.getBasePackageName(), TEST_USER_0_ID); assertTrue(task.mKillProcessesOnDestroyed); top.setState(ActivityRecord.State.DESTROYING, "test"); + bottom.destroyed("test"); + assertTrue("Wait for all destroyed", task.mKillProcessesOnDestroyed); top.destroyed("test"); - assertFalse(task.mKillProcessesOnDestroyed); + assertFalse("Consume kill", task.mKillProcessesOnDestroyed); // If the process is died, the state should be cleared. final Task lastTask = tasks.get(0); + lastTask.intent.setComponent(top.mActivityComponent); lastTask.addChild(top); lastTask.mKillProcessesOnDestroyed = true; top.handleAppDied(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 873d09b42c83..75716b913cee 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1201,7 +1201,7 @@ public class TransitionTests extends WindowTestsBase { final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); assertNotNull(asyncRotationController); - assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); + assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); assertTrue(app.getTask().inTransition()); player.start(); @@ -1226,6 +1226,7 @@ public class TransitionTests extends WindowTestsBase { assertFalse(asyncRotationController.isTargetToken(navBar.mToken)); navBar.finishDrawing(null /* postDrawTransaction */, Integer.MAX_VALUE); assertTrue(asyncRotationController.isTargetToken(navBar.mToken)); + assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar)); player.startTransition(); // Non-app windows should not be collected. diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index befc4a0120b1..4c978ad01a81 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -259,7 +259,24 @@ public class StorageStatsService extends IStorageStatsManager.Stub { // NOTE: No permissions required if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { - return FileUtils.roundStorageSize(mStorage.getPrimaryStorageSize()); + // As a safety measure, use the original implementation for the devices + // with storage size <= 512GB to prevent any potential regressions + final long roundedUserspaceBytes = mStorage.getPrimaryStorageSize(); + if (roundedUserspaceBytes <= DataUnit.GIGABYTES.toBytes(512)) { + return roundedUserspaceBytes; + } + + // Since 1TB devices can actually have either 1000GB or 1024GB, + // get the block device size and do just a small rounding if any at all + final long totalBytes = mStorage.getInternalStorageBlockDeviceSize(); + final long totalBytesRounded = FileUtils.roundStorageSize(totalBytes); + // If the storage size is 997GB-999GB, round it to a 1000GB to show + // 1TB in UI instead of 0.99TB. Same for 2TB, 4TB, 8TB etc. + if (totalBytesRounded - totalBytes <= DataUnit.GIGABYTES.toBytes(3)) { + return totalBytesRounded; + } else { + return totalBytes; + } } else { final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); if (vol == null) { @@ -286,15 +303,19 @@ public class StorageStatsService extends IStorageStatsManager.Stub { // Free space is usable bytes plus any cached data that we're // willing to automatically clear. To avoid user confusion, this // logic should be kept in sync with getAllocatableBytes(). + long freeBytes; if (isQuotaSupported(volumeUuid, PLATFORM_PACKAGE_NAME)) { final long cacheTotal = getCacheBytes(volumeUuid, PLATFORM_PACKAGE_NAME); final long cacheReserved = mStorage.getStorageCacheBytes(path, 0); final long cacheClearable = Math.max(0, cacheTotal - cacheReserved); - return path.getUsableSpace() + cacheClearable; + freeBytes = path.getUsableSpace() + cacheClearable; } else { - return path.getUsableSpace(); + freeBytes = path.getUsableSpace(); } + + Slog.d(TAG, "getFreeBytes: " + freeBytes); + return freeBytes; } finally { Binder.restoreCallingIdentity(token); } diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index a9cdf7e5bc08..94d4d2260d5c 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -791,7 +791,7 @@ public final class SmsApplication { AppOpsManager.MODE_ALLOWED); } } catch (NameNotFoundException e) { - // No whitelisted system app on this device + // No allowlisted system app on this device Log.e(LOG_TAG, "Package not found: " + packageName); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 4907134ffe1f..13e5ff15f1d8 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10408,7 +10408,7 @@ public class CarrierConfigManager { sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, new int[] {4 /* BUSY */}); sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false); - sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000); + sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 5000); sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]); sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP); @@ -10518,7 +10518,7 @@ public class CarrierConfigManager { auto_data_switch_rat_signal_score_string_bundle.putIntArray( "eHRPD", new int[]{10, 400, 600, 800, 1000}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( - "TD_SCDMA", new int[]{1, 100, 500, 1000}); + "TD_SCDMA", new int[]{1, 50, 100, 500, 1000}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "iDEN", new int[]{1, 2, 10, 50, 100}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( @@ -10536,7 +10536,7 @@ public class CarrierConfigManager { auto_data_switch_rat_signal_score_string_bundle.putIntArray( "EvDo_0", new int[]{300, 600, 1000, 1500, 2000}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( - "1xRTT", new int[]{50, 60, 70, 80}); + "1xRTT", new int[]{50, 60, 70, 80, 90}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "EDGE", new int[]{154, 169, 183, 192, 267}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 40488b15a37c..145cc2c78ed9 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -441,7 +441,7 @@ public final class SmsManager { } /** - * Get {@link Context#getOpPackageName()} if this manager has a context, otherwise a dummy + * Get {@link Context#getOpPackageName()} if this manager has a context, otherwise a placeholder * value. * * @return The package name to be used for app-ops checks diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index baacb574bde3..12ab5c32ad49 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3396,15 +3396,11 @@ public class SubscriptionManager { if (iSub != null) { groupUuid = iSub.createSubscriptionGroup(subIdArray, pkgForDebug); } else { - if (!isSystemProcess()) { - throw new IllegalStateException("telephony service is null."); - } + throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { loge("createSubscriptionGroup RemoteException " + ex); - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } + ex.rethrowAsRuntimeException(); } return groupUuid; @@ -3446,15 +3442,11 @@ public class SubscriptionManager { if (iSub != null) { iSub.addSubscriptionsIntoGroup(subIdArray, groupUuid, pkgForDebug); } else { - if (!isSystemProcess()) { - throw new IllegalStateException("telephony service is null."); - } + throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { loge("addSubscriptionsIntoGroup RemoteException " + ex); - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } + ex.rethrowAsRuntimeException(); } } @@ -3497,15 +3489,11 @@ public class SubscriptionManager { if (iSub != null) { iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, callingPackage); } else { - if (!isSystemProcess()) { - throw new IllegalStateException("telephony service is null."); - } + throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { loge("removeSubscriptionsFromGroup RemoteException " + ex); - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } + ex.rethrowAsRuntimeException(); } } @@ -3562,6 +3550,11 @@ public class SubscriptionManager { } } + // TODO(b/296125268) Really this method should throw, but it's common enough that for + // system callers it's worth having a little magic for the system process until it's + // made safer. + if (result == null) result = Collections.emptyList(); + return result; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 5f67441b533c..69b1d6333596 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -7857,7 +7857,7 @@ public class TelephonyManager { } /** - * Rollback modem configurations to factory default except some config which are in whitelist. + * Rollback modem configurations to factory default except some config which are in allowlist. * Used for device configuration by some carriers. * * <p>Requires Permission: @@ -15351,7 +15351,7 @@ public class TelephonyManager { * * 1) User data is turned on, or * 2) APN is un-metered for this subscription, or - * 3) APN type is whitelisted. E.g. MMS is whitelisted if + * 3) APN type is allowlisted. E.g. MMS is allowlisted if * {@link #MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED} is enabled. * * @param apnType Value indicating the apn type. Apn types are defined in {@link ApnSetting}. diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index dda021e6172f..67acda05382b 100644 --- a/telephony/java/android/telephony/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -460,7 +460,7 @@ public final class ImsReasonInfo implements Parcelable { */ public static final int CODE_LOW_BATTERY = 505; /** - * Device declined a call due to a blacklisted caller ID. + * Device declined a call due to a denylisted caller ID. */ public static final int CODE_BLACKLISTED_CALL_ID = 506; // IMS -> Telephony diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index c498216cf75b..9994002a4a37 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -57,7 +57,7 @@ import java.util.function.Consumer; */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE) @SystemApi -public class SatelliteManager { +public final class SatelliteManager { private static final String TAG = "SatelliteManager"; private static final ConcurrentHashMap<SatelliteDatagramCallback, ISatelliteDatagramCallback> @@ -1545,11 +1545,11 @@ public class SatelliteManager { */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void onDeviceAlignedWithSatellite(boolean isAligned) { + public void setDeviceAlignedWithSatellite(boolean isAligned) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - telephony.onDeviceAlignedWithSatellite(mSubId, isAligned); + telephony.setDeviceAlignedWithSatellite(mSubId, isAligned); } else { throw new IllegalStateException("telephony service is null."); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 0c3991d07ab6..06071feccf69 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2978,7 +2978,7 @@ interface ITelephony { */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void onDeviceAlignedWithSatellite(int subId, in boolean isAligned); + void setDeviceAlignedWithSatellite(int subId, in boolean isAligned); /** * This API can be used by only CTS to update satellite vendor service package name. diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 1a58f17ef6a0..fa452dd78873 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -674,7 +674,7 @@ public class AppLaunch extends InstrumentationTestCase { return true; } - // iorap compiler filters specified: the compilerFilter must be in the whitelist. + // iorap compiler filters specified: the compilerFilter must be in the allowlist. if (mIorapCompilerFilters.indexOf(compilerFilter) != -1) { return true; } diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt index ee587f5a8d4d..03352e36576e 100644 --- a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt +++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt @@ -112,10 +112,10 @@ class CompanionDeviceManagerSnippet : Snippet { throw CompanionException("Association result can't be null.") } - val association = result.getParcelableExtra( + val association = checkNotNull(result.getParcelableExtra( CompanionDeviceManager.EXTRA_ASSOCIATION, AssociationInfo::class.java - ) + )) val remoteDevice = association.associatedDevice?.getBluetoothDevice()!! // Register associated device @@ -138,8 +138,8 @@ class CompanionDeviceManagerSnippet : Snippet { */ @Rpc(description = "Start permissions sync.") fun startPermissionsSync(associationId: Int) { - val pendingIntent = companionDeviceManager - .buildPermissionTransferUserConsentIntent(associationId) + val pendingIntent = checkNotNull(companionDeviceManager + .buildPermissionTransferUserConsentIntent(associationId)) CompanionActivity.launchAndWait(context) CompanionActivity.startIntentSender(pendingIntent) confirmationUi.waitUntilSystemDataTransferConfirmationVisible() diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt index e56ce81e8ce6..63782f1bf955 100644 --- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt +++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt @@ -45,7 +45,7 @@ class UnresponsiveGestureMonitorActivity : Activity() { private lateinit var mInputMonitor: InputMonitor override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val inputManager = getSystemService(InputManager::class.java) + val inputManager = checkNotNull(getSystemService(InputManager::class.java)) mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId) mInputEventReceiver = UnresponsiveReceiver( mInputMonitor.getInputChannel(), Looper.myLooper()!!) diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java index 807f0c63668c..e60d8efdbfa4 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java @@ -23,6 +23,7 @@ import static com.android.inputmethod.stresstest.ImeStressTestUtil.UNFOCUSABLE_V import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync; import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters; import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify; import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet; import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden; import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus; @@ -225,7 +226,7 @@ public final class AutoShowTest { Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity firstActivity = TestActivity.start(intent1); // Request view focus after app starts - mInstrumentation.runOnMainSync(firstActivity::requestFocus); + requestFocusAndVerify(firstActivity); Intent intent2 = createIntent( @@ -252,7 +253,7 @@ public final class AutoShowTest { Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent1); // Request view focus after app starts - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); // Create second TestActivity Intent intent2 = @@ -284,7 +285,7 @@ public final class AutoShowTest { Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request view focus after app starts - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); // Find the editText and click it UiObject2 editTextUiObject = diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java index 320daeeb2e54..2ac25f2696d3 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java @@ -23,6 +23,7 @@ import static com.android.compatibility.common.util.SystemUtil.eventually; import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE; import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent; import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify; import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus; import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden; import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown; @@ -96,7 +97,7 @@ public final class DefaultImeVisibilityTest { UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); eventually( () -> - assertWithMessage("Display rotation should be updated.") + assertWithMessage("Display rotation should have been updated") .that(uiDevice.getDisplayRotation()) .isEqualTo(mIsPortrait ? 0 : 1), TIMEOUT); @@ -104,7 +105,7 @@ public final class DefaultImeVisibilityTest { for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { // TODO(b/291752364): Remove the explicit focus request once the issue with view focus // change between fullscreen IME and actual editText is fixed. - callOnMainSync(editText::requestFocus); + requestFocusAndVerify(activity); verifyWindowAndViewFocus(editText, true, true); callOnMainSync(activity::showImeWithInputMethodManager); waitOnMainUntilImeIsShown(editText); diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java index 5c0212400ff1..5368025ff898 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java @@ -29,6 +29,7 @@ import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSyn import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters; import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags; import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify; import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet; import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden; import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus; @@ -38,6 +39,9 @@ import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUnt import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; + import android.app.Instrumentation; import android.content.Intent; import android.os.Build; @@ -96,7 +100,8 @@ public final class ImeOpenCloseStressTest { Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); + // Test only once if window flags set to save time. int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS; for (int i = 0; i < iterNum; i++) { @@ -106,21 +111,19 @@ public final class ImeOpenCloseStressTest { verifyShowBehavior(activity); callOnMainSync(activity::hideImeWithInputMethodManager); - verifyHideBehavior(activity); } } @Test public void testShowHideWithInputMethodManager_waitingAnimationEnd() { + assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags)); + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); - if (hasUnfocusableWindowFlags(activity)) { - return; // Skip to save time. - } activity.enableAnimationMonitoring(); EditText editText = activity.getEditText(); for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { @@ -128,12 +131,12 @@ public final class ImeOpenCloseStressTest { Log.i(TAG, msgPrefix + "start"); callOnMainSync(activity::showImeWithInputMethodManager); waitOnMainUntil( - msgPrefix + "IME should be visible", + msgPrefix + "IME should have been shown", () -> !activity.isAnimating() && isImeShown(editText)); callOnMainSync(activity::hideImeWithInputMethodManager); waitOnMainUntil( - msgPrefix + "IME should be hidden", + msgPrefix + "IME should have been hidden", () -> !activity.isAnimating() && !isImeShown(editText)); } } @@ -141,13 +144,13 @@ public final class ImeOpenCloseStressTest { @Test public void testShowHideWithInputMethodManager_intervalAfterHide() { // Regression test for b/221483132 + assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags)); + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); - if (hasUnfocusableWindowFlags(activity)) { - return; // Skip to save time. - } + requestFocusAndVerify(activity); + // Intervals = 10, 20, 30, ..., 100, 150, 200, ... List<Integer> intervals = new ArrayList<>(); for (int i = 10; i < 100; i += 10) intervals.add(i); @@ -165,14 +168,12 @@ public final class ImeOpenCloseStressTest { @Test public void testShowHideWithInputMethodManager_inSameFrame() { + assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags)); Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); - if (hasUnfocusableWindowFlags(activity)) { - return; // Skip to save time. - } // hidden -> show -> hide mInstrumentation.runOnMainSync( () -> { @@ -256,13 +257,12 @@ public final class ImeOpenCloseStressTest { @Test public void testShowHideWithWindowInsetsController_waitingVisibilityChange() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); + // Test only once if window flags set to save time. int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS; for (int i = 0; i < iterNum; i++) { @@ -277,17 +277,13 @@ public final class ImeOpenCloseStressTest { @Test public void testShowHideWithWindowInsetsController_waitingAnimationEnd() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); + assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags)); Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); - if (hasUnfocusableWindowFlags(activity)) { - return; // Skip to save time. - } activity.enableAnimationMonitoring(); EditText editText = activity.getEditText(); for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { @@ -295,29 +291,25 @@ public final class ImeOpenCloseStressTest { Log.i(TAG, msgPrefix + "start"); mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); waitOnMainUntil( - msgPrefix + "IME should be visible", + msgPrefix + "IME should have been shown", () -> !activity.isAnimating() && isImeShown(editText)); mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController); waitOnMainUntil( - msgPrefix + "IME should be hidden", + msgPrefix + "IME should have been hidden", () -> !activity.isAnimating() && !isImeShown(editText)); } } @Test public void testShowHideWithWindowInsetsController_intervalAfterHide() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); + assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags)); Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); - if (hasUnfocusableWindowFlags(activity)) { - return; // Skip to save time. - } // Intervals = 10, 20, 30, ..., 100, 150, 200, ... List<Integer> intervals = new ArrayList<>(); for (int i = 10; i < 100; i += 10) intervals.add(i); @@ -335,17 +327,13 @@ public final class ImeOpenCloseStressTest { @Test public void testShowHideWithWindowInsetsController_inSameFrame() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); + assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags)); Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); // Request focus after app starts to avoid triggering auto-show behavior. - mInstrumentation.runOnMainSync(activity::requestFocus); + requestFocusAndVerify(activity); - if (hasUnfocusableWindowFlags(activity)) { - return; // Skip to save time. - } // hidden -> show -> hide mInstrumentation.runOnMainSync( () -> { @@ -377,9 +365,7 @@ public final class ImeOpenCloseStressTest { @Test public void testShowWithWindowInsetsController_onCreate_requestFocus() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); // Show with InputMethodManager at onCreate() Intent intent = createIntent( @@ -394,10 +380,8 @@ public final class ImeOpenCloseStressTest { @Test public void testShowWithWindowInsetsController_onCreate_notRequestFocus() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } - // Show and hide with InputMethodManager at onCreate() + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); + // Show and hide with WindowInsetsController at onCreate() Intent intent = createIntent( mWindowFocusFlags, @@ -411,10 +395,8 @@ public final class ImeOpenCloseStressTest { @Test public void testShowWithWindowInsetsController_afterStart_notRequestFocus() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } - // Show and hide with InputMethodManager at onCreate() + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); + // Show and hide with WindowInsetsController at onCreate() Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); TestActivity activity = TestActivity.start(intent); mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); @@ -425,7 +407,8 @@ public final class ImeOpenCloseStressTest { /** * Test IME hidden by calling show and hide IME consecutively with - * {@link android.view.WindowInsetsController} APIs in {@link android.app.Activity#onCreate}. + * {@link android.view.WindowInsetsController} APIs in + * {@link android.app.Activity#onCreate}. * * <p> Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED} * window flag to avoid some softInputMode visibility flags may take presence over @@ -436,13 +419,11 @@ public final class ImeOpenCloseStressTest { */ @Test public void testHideWithWindowInsetsController_onCreate_requestFocus() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return; - } + assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) { return; } - // Show and hide with InputMethodManager at onCreate() + // Show and hide with WindowInsetsController at onCreate() Intent intent = createIntent( mWindowFocusFlags, diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java index 12556bcf7ded..c0c60eff0f9f 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java @@ -40,6 +40,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.LinearLayout; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.platform.app.InstrumentationRegistry; @@ -149,6 +150,14 @@ public final class ImeStressTestUtil { } /** + * Requests EditText view focus on the main thread, and assert this returns {@code true}. + */ + public static void requestFocusAndVerify(TestActivity activity) { + boolean result = callOnMainSync(activity::requestFocus); + assertWithMessage("View focus request should have succeeded").that(result).isTrue(); + } + + /** * Waits until {@code pred} returns true, or throws on timeout. * * <p>The given {@code pred} will be called on the main thread. @@ -161,7 +170,7 @@ public final class ImeStressTestUtil { public static void waitOnMainUntilImeIsShown(View view) { eventually( () -> - assertWithMessage("IME should be shown") + assertWithMessage("IME should have been shown") .that(callOnMainSync(() -> isImeShown(view))) .isTrue(), TIMEOUT); @@ -171,27 +180,28 @@ public final class ImeStressTestUtil { public static void waitOnMainUntilImeIsHidden(View view) { eventually( () -> - assertWithMessage("IME should be hidden") + assertWithMessage("IME should have been hidden") .that(callOnMainSync(() -> isImeShown(view))) .isFalse(), TIMEOUT); } - /** Waits until window get focus, or throws on timeout. */ + /** Waits until window gains focus, or throws on timeout. */ public static void waitOnMainUntilWindowGainsFocus(View view) { eventually( () -> - assertWithMessage("Window should gain focus") + assertWithMessage( + "Window should have gained focus; value of hasWindowFocus:") .that(callOnMainSync(view::hasWindowFocus)) .isTrue(), TIMEOUT); } - /** Waits until view get focus, or throws on timeout. */ + /** Waits until view gains focus, or throws on timeout. */ public static void waitOnMainUntilViewGainsFocus(View view) { eventually( () -> - assertWithMessage("View should gain focus") + assertWithMessage("View should have gained focus; value of hasFocus:") .that(callOnMainSync(view::hasFocus)) .isTrue(), TIMEOUT); @@ -201,7 +211,7 @@ public final class ImeStressTestUtil { public static void verifyImeIsAlwaysHidden(View view) { always( () -> - assertWithMessage("IME should be hidden") + assertWithMessage("IME should have been hidden") .that(callOnMainSync(() -> isImeShown(view))) .isFalse(), TIMEOUT); @@ -211,7 +221,8 @@ public final class ImeStressTestUtil { public static void verifyWindowNeverGainsFocus(View view) { always( () -> - assertWithMessage("window should never gain focus") + assertWithMessage( + "Window should not have gained focus; value of hasWindowFocus:") .that(callOnMainSync(view::hasWindowFocus)) .isFalse(), TIMEOUT); @@ -221,7 +232,7 @@ public final class ImeStressTestUtil { public static void verifyViewNeverGainsFocus(View view) { always( () -> - assertWithMessage("view should never gain ime focus") + assertWithMessage("View should not have gained focus; value of hasFocus:") .that(callOnMainSync(view::hasFocus)) .isFalse(), TIMEOUT); @@ -254,8 +265,23 @@ public final class ImeStressTestUtil { } } + /** + * Returns {@code true} if the activity can't receive IME focus, based on its window flags, + * and {@code false} otherwise. + * + * @param activity the activity to check. + */ public static boolean hasUnfocusableWindowFlags(Activity activity) { - int windowFlags = activity.getWindow().getAttributes().flags; + return hasUnfocusableWindowFlags(activity.getWindow().getAttributes().flags); + } + + /** + * Returns {@code true} if the activity can't receive IME focus, based on its window flags, + * and {@code false} otherwise. + * + * @param windowFlags the window flags to check. + */ + public static boolean hasUnfocusableWindowFlags(int windowFlags) { return (windowFlags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0 || (windowFlags & LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 || (windowFlags & LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; @@ -302,22 +328,26 @@ public final class ImeStressTestUtil { private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + @NonNull @Override public WindowInsetsAnimation.Bounds onStart( - WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) { + @NonNull WindowInsetsAnimation animation, + @NonNull WindowInsetsAnimation.Bounds bounds) { mIsAnimating = true; return super.onStart(animation, bounds); } @Override - public void onEnd(WindowInsetsAnimation animation) { + public void onEnd(@NonNull WindowInsetsAnimation animation) { super.onEnd(animation); mIsAnimating = false; } + @NonNull @Override public WindowInsets onProgress( - WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) { + @NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { return insets; } }; @@ -394,7 +424,7 @@ public final class ImeStressTestUtil { getInputMethodManager() .showSoftInput(mEditText, 0 /* flags */); if (showResult) { - Log.i(TAG, "IMM#showSoftInput successfully"); + Log.i(TAG, "IMM#showSoftInput succeeded"); } else { Log.i(TAG, "IMM#showSoftInput failed"); } @@ -407,7 +437,7 @@ public final class ImeStressTestUtil { getInputMethodManager() .hideSoftInputFromWindow(mEditText.getWindowToken(), 0 /* flags */); if (hideResult) { - Log.i(TAG, "IMM#hideSoftInput successfully"); + Log.i(TAG, "IMM#hideSoftInput succeeded"); } else { Log.i(TAG, "IMM#hideSoftInput failed"); } @@ -421,7 +451,7 @@ public final class ImeStressTestUtil { } Log.i(TAG, "showImeWithWIC()"); WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController(); - assertWithMessage("WindowInsetsController shouldn't be null.") + assertWithMessage("WindowInsetsController") .that(windowInsetsController) .isNotNull(); windowInsetsController.show(WindowInsets.Type.ime()); @@ -434,7 +464,7 @@ public final class ImeStressTestUtil { } Log.i(TAG, "hideImeWithWIC()"); WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController(); - assertWithMessage("WindowInsetsController shouldn't be null.") + assertWithMessage("WindowInsetsController") .that(windowInsetsController) .isNotNull(); windowInsetsController.hide(WindowInsets.Type.ime()); @@ -482,13 +512,14 @@ public final class ImeStressTestUtil { return mIsAnimating; } - public void requestFocus() { - boolean requestFocusResult = getEditText().requestFocus(); + public boolean requestFocus() { + boolean requestFocusResult = mEditText.requestFocus(); if (requestFocusResult) { - Log.i(TAG, "Request focus successfully"); + Log.i(TAG, "View#requestFocus succeeded"); } else { - Log.i(TAG, "Request focus failed"); + Log.i(TAG, "View#requestFocus failed"); } + return requestFocusResult; } } } diff --git a/tests/Internal/src/stub/DummyWallpaperService.java b/tests/Internal/src/stub/DummyWallpaperService.java index 084c036bea26..db1b7805a316 100644 --- a/tests/Internal/src/stub/DummyWallpaperService.java +++ b/tests/Internal/src/stub/DummyWallpaperService.java @@ -19,7 +19,7 @@ package stub; import android.service.wallpaper.WallpaperService; /** - * Dummy wallpaper service only for test purposes, won't draw anything. + * Placeholder wallpaper service only for test purposes, won't draw anything. */ public class DummyWallpaperService extends WallpaperService { @Override diff --git a/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java b/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java index 731be8e3d9f0..a77950fe4a2d 100644 --- a/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java +++ b/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java @@ -24,7 +24,7 @@ import android.os.ServiceManager; import com.android.internal.compat.IPlatformCompat; /** - * This is a dummy API to test gating + * This is a placeholder API to test gating * * @hide */ @@ -36,7 +36,7 @@ public class DummyApi { public static final long CHANGE_SYSTEM_SERVER = 666016; /** - * Dummy method + * Placeholder method * @return "A" if change is enabled, "B" otherwise. */ public static String dummyFunc() { @@ -47,7 +47,7 @@ public class DummyApi { } /** - * Dummy combined method + * Placeholder combined method * @return "0" if {@link CHANGE_ID_1} is disabled and {@link CHANGE_ID_2} is disabled, "1" if {@link CHANGE_ID_1} is disabled and {@link CHANGE_ID_2} is enabled, "2" if {@link CHANGE_ID_1} is enabled and {@link CHANGE_ID_2} is disabled, @@ -68,7 +68,7 @@ public class DummyApi { } /** - * Dummy api using system server API. + * Placeholder api using system server API. */ public static boolean dummySystemServer(Context context) { IPlatformCompat platformCompat = IPlatformCompat.Stub diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt index c4bc6001533e..43debb11013a 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt @@ -98,23 +98,23 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { (view.getParent() as ViewGroup).removeView(view) parent.addView(view) - view.findViewById<Button>(R.id.gainmap_metadata_done)!!.setOnClickListener { + view.requireViewById<Button>(R.id.gainmap_metadata_done).setOnClickListener { closeEditor() } - view.findViewById<Button>(R.id.gainmap_metadata_reset)!!.setOnClickListener { + view.requireViewById<Button>(R.id.gainmap_metadata_reset).setOnClickListener { resetGainmapMetadata() } updateMetadataUi() - val gainmapMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) - val gainmapMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) - val capacityMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) - val capacityMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) - val gammaSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gamma) - val offsetSdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) - val offsetHdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) + val gainmapMinSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) + val gainmapMaxSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) + val capacityMinSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) + val capacityMaxSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) + val gammaSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gamma) + val offsetSdrSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) + val offsetHdrSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek, offsetSdrSeek, offsetHdrSeek).forEach { it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{ @@ -139,13 +139,13 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { } private fun updateMetadataUi() { - val gainmapMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) - val gainmapMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) - val capacityMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) - val capacityMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) - val gammaSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gamma) - val offsetSdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) - val offsetHdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) + val gainmapMinSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) + val gainmapMaxSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) + val capacityMinSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) + val capacityMaxSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) + val gammaSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gamma) + val offsetSdrSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) + val offsetHdrSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) gainmapMinSeek.setProgress( ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt()) @@ -165,19 +165,19 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0) .toFloat() * maxProgress).toInt()) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val).setText( "%.3f".format(currentMetadata.ratioMin)) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val).setText( "%.3f".format(currentMetadata.ratioMax)) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymin_val).setText( "%.3f".format(currentMetadata.capacityMin)) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymax_val).setText( "%.3f".format(currentMetadata.capacityMax)) - parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gamma_val).setText( "%.3f".format(currentMetadata.gamma)) - parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val).setText( "%.5f".format(currentMetadata.offsetSdr)) - parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsethdr_val).setText( "%.5f".format(currentMetadata.offsetHdr)) } @@ -200,7 +200,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateGainmapMin(normalized: Float) { val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val).setText( "%.3f".format(newValue)) currentMetadata.ratioMin = newValue gainmap.setRatioMin(newValue, newValue, newValue) @@ -209,7 +209,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateGainmapMax(normalized: Float) { val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val).setText( "%.3f".format(newValue)) currentMetadata.ratioMax = newValue gainmap.setRatioMax(newValue, newValue, newValue) @@ -218,7 +218,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateCapacityMin(normalized: Float) { val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymin_val).setText( "%.3f".format(newValue)) currentMetadata.capacityMin = newValue gainmap.setMinDisplayRatioForHdrTransition(newValue) @@ -227,7 +227,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateCapacityMax(normalized: Float) { val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymax_val).setText( "%.3f".format(newValue)) currentMetadata.capacityMax = newValue gainmap.setDisplayRatioForFullHdr(newValue) @@ -236,7 +236,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateGamma(normalized: Float) { val newValue = minGamma + normalized * (maxGamma - minGamma) - parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gamma_val).setText( "%.3f".format(newValue)) currentMetadata.gamma = newValue gainmap.setGamma(newValue, newValue, newValue) @@ -248,7 +248,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { if (normalized > 0.0f ) { newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat() } - parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val).setText( "%.5f".format(newValue)) currentMetadata.offsetSdr = newValue gainmap.setEpsilonSdr(newValue, newValue, newValue) @@ -260,7 +260,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { if (normalized > 0.0f ) { newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat() } - parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsethdr_val).setText( "%.5f".format(newValue)) currentMetadata.offsetHdr = newValue gainmap.setEpsilonHdr(newValue, newValue, newValue) diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt index 2f2578b87f35..41baeadf7a8c 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt @@ -190,9 +190,9 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont sensorManager?.unregisterListener(sensorListener) } - override fun onDraw(canvas: Canvas?) { + override fun onDraw(canvas: Canvas) { updateGlassRenderNode() - canvas?.drawRenderNode(renderNode) + canvas.drawRenderNode(renderNode) } fun resetGyroOffsets() { @@ -227,4 +227,4 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont renderNodeIsDirty = false } } -}
\ No newline at end of file +} diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt index 1400dde5781d..c1a7bd9e1d8f 100644 --- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt @@ -30,7 +30,7 @@ import org.junit.runners.model.Statement */ class LockStateTrackingRule : TestRule { private val context: Context = getApplicationContext() - private val windowManager = WindowManagerGlobal.getWindowManagerService() + private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService()) @Volatile lateinit var lockState: LockState private set diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt index 4189baae10cb..f1edca3ff86e 100644 --- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt @@ -36,7 +36,7 @@ import org.junit.runners.model.Statement class ScreenLockRule : TestRule { private val context: Context = getApplicationContext() private val uiDevice = UiDevice.getInstance(getInstrumentation()) - private val windowManager = WindowManagerGlobal.getWindowManagerService() + private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService()) private val lockPatternUtils = LockPatternUtils(context) private var instantLockSavedValue = false diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp index 0d2f2ef46cab..af5676df49bc 100644 --- a/tests/UiBench/Android.bp +++ b/tests/UiBench/Android.bp @@ -10,7 +10,10 @@ package { android_test { name: "UiBench", sdk_version: "current", - min_sdk_version: "21", + // As Perfetto trace recording is supported on non-rooted devices + // since Android 12. Set min/target sdk version to 31. + target_sdk_version: "31", + min_sdk_version: "31", // omit gradle 'build' dir srcs: ["src/**/*.java"], // use appcompat/support lib from the tree, so improvements/ diff --git a/tests/utils/testutils/java/com/android/internal/util/test/LocalServiceKeeperRule.java b/tests/utils/testutils/java/com/android/internal/util/test/LocalServiceKeeperRule.java new file mode 100644 index 000000000000..7012daf586d8 --- /dev/null +++ b/tests/utils/testutils/java/com/android/internal/util/test/LocalServiceKeeperRule.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.test; + +import com.android.server.LocalServices; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * JUnit Rule helps override /restore {@link LocalServices} state. + */ +public class LocalServiceKeeperRule implements TestRule { + + private final Map<Class<?>, Object> mOverriddenServices = new HashMap<>(); + private final List<Class<?>> mAddedServices = new ArrayList<>(); + + private volatile boolean mRuleApplied = false; + + /** + * Overrides service in LocalServices. Service will be restored to original after test run. + */ + public <T> void overrideLocalService(Class<T> type, T service) { + if (!mRuleApplied) { + throw new IllegalStateException("Can't override service without applying rule"); + } + if (mOverriddenServices.containsKey(type) || mAddedServices.contains(type)) { + throw new IllegalArgumentException("Type already overridden: " + type); + } + + T currentService = LocalServices.getService(type); + if (currentService != null) { + mOverriddenServices.put(type, currentService); + LocalServices.removeServiceForTest(type); + } else { + mAddedServices.add(type); + } + LocalServices.addService(type, service); + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + public void evaluate() throws Throwable { + try { + mRuleApplied = true; + base.evaluate(); + } finally { + mRuleApplied = false; + mAddedServices.forEach(LocalServices::removeServiceForTest); + mOverriddenServices.forEach((clazz, service) -> { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService((Class) clazz, service); + }); + mAddedServices.clear(); + mOverriddenServices.clear(); + } + } + }; + } +} diff --git a/tests/utils/testutils/tests/src/com/android/internal/util/test/LocalServiceKeeperRuleTest.java b/tests/utils/testutils/tests/src/com/android/internal/util/test/LocalServiceKeeperRuleTest.java new file mode 100644 index 000000000000..12f2fae0a581 --- /dev/null +++ b/tests/utils/testutils/tests/src/com/android/internal/util/test/LocalServiceKeeperRuleTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +import androidx.test.filters.SmallTest; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +@SmallTest +public class LocalServiceKeeperRuleTest { + + private final Description mDescription = Description.createSuiteDescription("Description"); + + LocalServiceKeeperRule mRule = new LocalServiceKeeperRule(); + + @After + public void tearDown() { + LocalServices.removeServiceForTest(TestService.class); + } + + @Test + public void testFailedIfCalledOutsideOfTheRule() { + TestService service = new TestService() {}; + + assertThrows(IllegalStateException.class, + () -> mRule.overrideLocalService(TestService.class, service)); + } + + @Test + public void testSetsLocalServiceIfNotPresent() throws Throwable { + LocalServices.removeServiceForTest(TestService.class); + TestService service = new TestService() {}; + + runInRuleApply(() -> { + mRule.overrideLocalService(TestService.class, service); + assertEquals(service, LocalServices.getService(TestService.class)); + }); + } + + @Test + public void testOverridesLocalServiceIfPresent() throws Throwable { + TestService service = new TestService() {}; + LocalServices.addService(TestService.class, service); + TestService overriddenService = new TestService() {}; + + runInRuleApply(() -> { + mRule.overrideLocalService(TestService.class, overriddenService); + assertEquals(overriddenService, LocalServices.getService(TestService.class)); + }); + } + + @Test + public void testDoesNotAllowToOverrideSameServiceTwice() throws Throwable { + TestService service = new TestService() {}; + + runInRuleApply(() -> { + mRule.overrideLocalService(TestService.class, service); + assertThrows(IllegalArgumentException.class, + () -> mRule.overrideLocalService(TestService.class, service)); + }); + } + + @Test + public void testRestroresLocalServiceAfterTestIfPresent() throws Throwable { + TestService expectedService = new TestService() {}; + LocalServices.addService(TestService.class, expectedService); + TestService overriddenService = new TestService() {}; + + runInRuleApply(() -> mRule.overrideLocalService(TestService.class, overriddenService)); + + assertEquals(expectedService, LocalServices.getService(TestService.class)); + } + + @Test + public void testRemovesLocalServiceAfterTestIfNotPresent() throws Throwable { + LocalServices.removeServiceForTest(TestService.class); + TestService service = new TestService() {}; + + runInRuleApply(() -> mRule.overrideLocalService(TestService.class, service)); + + assertNull(LocalServices.getService(TestService.class)); + } + + private void runInRuleApply(Runnable runnable) throws Throwable { + Statement testStatement = new Statement() { + @Override + public void evaluate() { + runnable.run(); + } + }; + mRule.apply(testStatement, mDescription).evaluate(); + } + + interface TestService { + } +} diff --git a/tools/aapt/Android.bp b/tools/aapt/Android.bp index cc10db9e1523..cecd95a5e616 100644 --- a/tools/aapt/Android.bp +++ b/tools/aapt/Android.bp @@ -97,6 +97,7 @@ cc_library_host_static { "ResourceTable.cpp", "SourcePos.cpp", "StringPool.cpp", + "Utils.cpp", "WorkQueue.cpp", "XMLNode.cpp", "ZipEntry.cpp", diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 965655b59a94..a7ff5fabf495 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -8,6 +8,7 @@ #include "OutputSet.h" #include "ResourceTable.h" #include "ResourceFilter.h" +#include "Utils.h" #include <androidfw/misc.h> @@ -226,7 +227,7 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& o fprintf(stderr, "warning: null file being processed.\n"); } else { String8 storagePath(entry.getPath()); - storagePath.convertToResPath(); + convertToResPath(storagePath); if (!processFile(bundle, zip, storagePath, entry.getFile())) { return UNKNOWN_ERROR; } diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 4ca3a68d02a6..9c944e0de075 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -15,6 +15,7 @@ #include "ResourceTable.h" #include "StringPool.h" #include "Symbol.h" +#include "Utils.h" #include "WorkQueue.h" #include "XMLNode.h" @@ -321,7 +322,7 @@ static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets, str++; } String8 resPath = it.getPath(); - resPath.convertToResPath(); + convertToResPath(resPath); status_t result = table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()), type16, diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 23440074a326..449e0808806e 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -11,6 +11,7 @@ #include "ResourceFilter.h" #include "ResourceIdCache.h" #include "SdkConstants.h" +#include "Utils.h" #include <algorithm> #include <androidfw/ResourceTypes.h> @@ -4803,7 +4804,7 @@ bool ResourceTable::versionForCompat(const Bundle* bundle, const String16& resou String8 resPath = String8::format("res/%s/%s.xml", newFile->getGroupEntry().toDirName(target->getResourceType()).c_str(), String8(resourceName).c_str()); - resPath.convertToResPath(); + convertToResPath(resPath); // Add a resource table entry. addEntry(SourcePos(), @@ -4927,7 +4928,7 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, String8 resPath = String8::format("res/%s/%s.xml", newFile->getGroupEntry().toDirName(target->getResourceType()).c_str(), String8(resourceName).c_str()); - resPath.convertToResPath(); + convertToResPath(resPath); // Add a resource table entry. if (bundle->getVerbose()) { diff --git a/tools/aapt/Utils.cpp b/tools/aapt/Utils.cpp new file mode 100644 index 000000000000..36b018e7dd2c --- /dev/null +++ b/tools/aapt/Utils.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.h" + +#include <utils/Compat.h> + +// Separator used by resource paths. This is not platform dependent contrary +// to OS_PATH_SEPARATOR. +#define RES_PATH_SEPARATOR '/' + +using android::String8; + +void convertToResPath([[maybe_unused]] String8& s) { +#if OS_PATH_SEPARATOR != RES_PATH_SEPARATOR + size_t len = s.length(); + if (len > 0) { + char* buf = s.lockBuffer(len); + for (char* end = buf + len; buf < end; ++buf) { + if (*buf == OS_PATH_SEPARATOR) *buf = RES_PATH_SEPARATOR; + } + s.unlockBuffer(len); + } +#endif +} diff --git a/tools/aapt/Utils.h b/tools/aapt/Utils.h new file mode 100644 index 000000000000..8eb594138478 --- /dev/null +++ b/tools/aapt/Utils.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +// This file contains cruft that used to be in libutils' String8, that's only +// used for aapt. + +#include <utils/String8.h> + +// Converts all separators in this string to /, the default path +// separator. +// If the default OS separator is backslash, this converts all +// backslashes to slashes, in-place. Otherwise it does nothing. +void convertToResPath(android::String8&); |